unit Map;

interface

uses
  AM, PrinterDPI, SetCoordinates, CharterFailure, RenameCanvas, DBClient,
  Database, RProperties, NewMap, MapProperties, ConfirmLink, MapName, Saving,
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Menus, ComCtrls, StdCtrls, Buttons, Mask, ExtCtrls,
  ColorGrd, Printers, Grids, DB,
  DBCtrls, DBTables, MapDelete, EnterText, ShapedButton, FMTBcd, SqlExpr,
  SimpleDS, Generics.Collections;

type
  TIdDic = TDictionary<string, integer>;

  TFormMap = class(TForm)
    MainMenu: TMainMenu;                                          
    MenuInvisible: TMenuItem;
    MenuHelp: TMenuItem;
    MenuEscape: TMenuItem;
    MenuCtrlTab: TMenuItem;
    MenuCtrlTab2: TMenuItem;
    MenuAutotyper: TMenuItem;
    MenuChat: TMenuItem;
    MenuTell: TMenuItem;
    MenuImagery: TMenuItem;
    MenuFiltered: TMenuItem;
    MenuPanic: TMenuItem;
    N1: TMenuItem;
    MenuUp: TMenuItem;
    MenuRight: TMenuItem;
    MenuDown: TMenuItem;
    MenuLeft: TMenuItem;
    MenuZoomIn: TMenuItem;
    MenuZoomOut: TMenuItem;
    PopupMenu: TPopupMenu;
    PopupDelete: TMenuItem;
    PopupProperties: TMenuItem;
    DialogPrint: TPrintDialog;
    DialogSave: TSaveDialog;
    DialogOpen: TOpenDialog;
    MenuMap1: TMenuItem;                                                                            
    MenuNewMap: TMenuItem;
    MenuExport: TMenuItem;
    MenuImport: TMenuItem;
    MenuClose: TMenuItem;
    N5: TMenuItem;
    N6: TMenuItem;
    MenuOptions1: TMenuItem;
    MenuStayOnTop: TMenuItem;
    MenuDeleteMap: TMenuItem;
    MenuHelp1: TMenuItem;
    MenuMapProperties: TMenuItem;
    N3: TMenuItem;
    N4: TMenuItem;
    MenuUp5: TMenuItem;
    MenuRight5: TMenuItem;
    MenuDown5: TMenuItem;
    MenuLeft5: TMenuItem;
    MenuUndo: TMenuItem;
    MenuEdit1: TMenuItem;
    N7: TMenuItem;
    PopupSetLocation: TMenuItem;
    MenuDelete: TMenuItem;
    N8: TMenuItem;
    PopupSprint: TMenuItem;
    N9: TMenuItem;
    MenuDefault: TMenuItem;
    MenuProperties: TMenuItem;
    MenuSetLocation: TMenuItem;
    MenuSprint: TMenuItem;
    TimerActivating: TTimer;
    MenuPrint: TMenuItem;
    MenuDelete2: TMenuItem;
    PopupDelete2: TMenuItem;
    N2: TMenuItem;
    MenuTurnOnAll: TMenuItem;
    MenuTurnAllOn: TMenuItem;
    RoomLabels1: TMenuItem;
    RoomTags1: TMenuItem;
    ExitStubs1: TMenuItem;
    LinksLabels1: TMenuItem;
    SpeedEdit1: TMenuItem;
    MenuTurnOffAll: TMenuItem;
    MenuTurnAllOff: TMenuItem;
    RoomLabels2: TMenuItem;
    RoomTags2: TMenuItem;
    ExitStubs2: TMenuItem;
    LinkksLabels1: TMenuItem;
    SpeedEdit2: TMenuItem;
    N10: TMenuItem;
    PopupStayOnTop: TMenuItem;
    PanelMain: TPanel;
    PanelBase: TPanel;
    MenuZero: TMenuItem;
    DialogFont: TFontDialog;
    MenuFont: TMenuItem;
    MenuSelection: TMenuItem;
    MenuClearSelections: TMenuItem;
    N12: TMenuItem;
    MenuSelectionProperties: TMenuItem;
    NN1: TMenuItem;
    EE1: TMenuItem;
    SS1m: TMenuItem;
    WW1: TMenuItem;
    PopupSelect: TMenuItem;
    MenuSelectRoom: TMenuItem;
    MenuDeselectLast: TMenuItem;
    PanelStatus: TPanel;
    LabelStatus: TStaticText;
    Canvas1: TMenuItem;
    TimerHighlightStubs: TTimer;
    MenuBatchExport: TMenuItem;
    N13: TMenuItem;
    MenuCompleteStubs: TMenuItem;
    MenuHighlight: TMenuItem;
    PanelRight: TPanel;
    PanelToolbar: TPanel;
    fr0: TSpeedButton;
    fr1: TSpeedButton;
    fr2: TSpeedButton;
    fr3: TSpeedButton;
    fr4: TSpeedButton;
    ColorGrid: TColorGrid;
    BtnN: TShapedButton;
    BtnE: TShapedButton;
    BtnS: TShapedButton;
    BtnW: TShapedButton;
    PanelSelection: TPanel;
    BtnSelectionE: TBitBtn;
    BtnSelectionN: TBitBtn;
    BtnSelectionW: TBitBtn;
    BtnSelectionS: TBitBtn;
    BtnSelectionProperties: TBitBtn;
    BtnDeselect: TBitBtn;
    View1: TMenuItem;
    N14: TMenuItem;
    N15: TMenuItem;
    SGrid: TMenuItem;
    SLabels: TMenuItem;
    STags: TMenuItem;
    SStubs: TMenuItem;
    SLL: TMenuItem;
    SSpeed: TMenuItem;
    N16: TMenuItem;
    N17: TMenuItem;
    Bevel6: TBevel;
    PanelExample: TPanel;
    ImageStyle: TImage;
    ShapeRoom: TShape;
    ShapeLink: TShape;
    N11: TMenuItem;
    N18: TMenuItem;
    SetPrinterDPI1: TMenuItem;
    BtnNW: TShapedButton;
    BtnNE: TShapedButton;
    BtnSE: TShapedButton;
    BtnSW: TShapedButton;
    PanelBaseLeft: TPanel;
    MapCanvas: TImage;
    PanelLeft: TPanel;
    Ruler2: TImage;
    Ruler2Pointer: TImage;
    Ruler2Pointer2: TImage;
    PanelTop: TPanel;
    PanelRuler1: TPanel;
    Ruler1: TImage;
    Ruler1Pointer: TImage;
    Ruler1Pointer2: TImage;
    PanelBtnC2: TPanel;
    Image3: TImage;
    PanelTools: TPanel;
    MenuHome: TMenuItem;
    N20: TMenuItem;
    MenuSetHome: TMenuItem;
    Style1: TImage;
    Style2: TImage;
    Style3: TImage;
    Style4: TImage;
    TimerCountdown: TTimer;
    Bevel13: TBevel;
    BtnSprint: TSpeedButton;
    MenuParameters: TMenuItem;
    N21: TMenuItem;
    ClearMapLevel1: TMenuItem;
    Charting1: TMenuItem;
    MenuSpeedChart: TMenuItem;
    MenuTextChart: TMenuItem;
    MenuFreeChart: TMenuItem;
    MenuCenterChart: TMenuItem;
    PanelPosition: TPanel;
    LabelPosition: TStaticText;
    Bevel7: TBevel;
    PanelCharting: TPanel;
    Bevel2: TBevel;
    Panel1: TPanel;
    ImageStubs: TImage;
    LabelClock: TStaticText;
    BtnChart: TSpeedButton;
    BtnFollow: TSpeedButton;
    Bevel5: TBevel;
    Bevel8: TBevel;
    BtnHome: TSpeedButton;
    BtnSethome: TSpeedButton;
    BtnZoomIn: TSpeedButton;
    BtnZoomOut: TSpeedButton;
    BtnHelp: TSpeedButton;
    BtnX4: TSpeedButton;
    BtnX8: TSpeedButton;
    BtnNewMap: TSpeedButton;
    BtnPrint: TSpeedButton;
    BtnHighlight: TSpeedButton;
    BtnAutocomplete: TSpeedButton;
    Bevel1: TBevel;
    SBasement: TMenuItem;
    ShowBasement1: TMenuItem;
    ShowBasement2: TMenuItem;
    N22: TMenuItem;
    N23: TMenuItem;
    MenuBasement: TMenuItem;
    N24: TMenuItem;
    MenuAttic: TMenuItem;
    N25: TMenuItem;
    MenuLineAttic: TMenuItem;
    MenuLineBasement: TMenuItem;
    BtnAttic: TSpeedButton;
    BtnBasement: TSpeedButton;
    Bevel3: TBevel;
    N26: TMenuItem;
    MenuViewAttic: TMenuItem;
    MenuViewBasement: TMenuItem;
    fr5: TSpeedButton;
    Style5: TImage;
    Bevel4: TBevel;
    BtnEdit: TSpeedButton;
    SAttic: TMenuItem;
    ShowAttic1: TMenuItem;
    ShowAttic2: TMenuItem;
    MenuEdit: TMenuItem;
    N27: TMenuItem;
    MenuRenameMap: TMenuItem;
    MenuBatchDelete: TMenuItem;
    DialogSave2: TSaveDialog;
    SaveBitmapImageofMap1: TMenuItem;
    PopupCopySprint: TMenuItem;
    N28: TMenuItem;
    MenuFindText: TMenuItem;
    Sprint1: TMenuItem;
    MenuExecuteSprint: TMenuItem;
    MenuCopySprint: TMenuItem;
    N29: TMenuItem;
    ListMaps: TComboBox;
    dsCurrentMap: TClientDataSet;
    procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure BtnHelpClick(Sender: TObject);
    procedure MenuStayOnTopClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure MapCanvasMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure MapCanvasMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure MapCanvasMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure BtnCClick(Sender: TObject);
    procedure MenuExitClick(Sender: TObject);
    procedure MenuCtrlTabClick(Sender: TObject);
    procedure MenuEscapeClick(Sender: TObject);
    procedure MenuPanicClick(Sender: TObject);
    procedure MenuAutotyperClick(Sender: TObject);
    procedure PopupMenuPopup(Sender: TObject);
    procedure PopupDeleteClick(Sender: TObject);
    procedure PopupPropertiesClick(Sender: TObject);
    procedure BtnPrintClick(Sender: TObject);
    procedure MenuNewMapClick(Sender: TObject);
    procedure MenuExportClick(Sender: TObject);
    procedure MenuImportClick(Sender: TObject);
    procedure MenuMapPropertiesClick(Sender: TObject);
    procedure MenuZoomInClick(Sender: TObject);
    procedure MenuUpClick(Sender: TObject);
    procedure MenuDeleteMapClick(Sender: TObject);
    procedure BtnUndoClick(Sender: TObject);
    procedure PopupSetLocationClick(Sender: TObject);
    procedure PopupSprintClick(Sender: TObject);
    procedure MenuDefaultClick(Sender: TObject);
    procedure MenuPropertiesClick(Sender: TObject);
    procedure TimerActivatingTimer(Sender: TObject);
    procedure PopupCopySprintClick(Sender: TObject);
    procedure MenuTurnAllOnClick(Sender: TObject);
    procedure BtnFontClick(Sender: TObject);
    procedure BtnSelectionNClick(Sender: TObject);
    procedure MenuClearSelectionsClick(Sender: TObject);
    procedure MenuSelectionPropertiesClick(Sender: TObject);
    procedure NN1Click(Sender: TObject);
    procedure PopupSelectClick(Sender: TObject);
    procedure MenuDeselectLastClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure TimerHighlightStubsTimer(Sender: TObject);
    procedure MenuBatchExportClick(Sender: TObject);
    procedure MenuCompleteStubsClick(Sender: TObject);
    procedure ListMapsDropDown(Sender: TObject);
    procedure ListMapsCloseUp(Sender: TObject);
    procedure MenuHighlightClick(Sender: TObject);
    procedure BtnSetHomeClick(Sender: TObject);
    procedure SGridClick(Sender: TObject);
    procedure fr0Click(Sender: TObject);
    procedure SetPrinterDPI1Click(Sender: TObject);
    procedure BtnSprintClick(Sender: TObject);
    procedure BtnChartClick(Sender: TObject);
    procedure TimerCountdownTimer(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure MenuSpeedChartClick(Sender: TObject);
    procedure MenuParametersClick(Sender: TObject);
    procedure MenuBasementClick(Sender: TObject);
    procedure MenuLineAtticClick(Sender: TObject);
    procedure BtnAtticClick(Sender: TObject);
    procedure BtnEditClick(Sender: TObject);
    procedure MenuEditClick(Sender: TObject);
    procedure MenuZeroClick(Sender: TObject);
    procedure MenuRenameMapClick(Sender: TObject);
    procedure MenuBatchDeleteClick(Sender: TObject);
    procedure SaveBitmapImageofMap1Click(Sender: TObject);
    procedure MenuFindTextClick(Sender: TObject);
  private
    { Private declarations }
    FRoomDic: TIdDic;
    FMapDic: TIdDic;
    FLastMouseLoc: TPoint;
    FNewIDCounter: integer;
    procedure WMGetMinMaxInfo(var MSG: Tmessage); message WM_GetMinMaxInfo;
    procedure WMEnterSizeMove(var MSG: Tmessage);  message WM_ENTERSIZEMOVE;
    procedure WMExitSizeMove(var MSG: Tmessage);  message WM_EXITSIZEMOVE;
    procedure PlotStubs(x, y, d, offset: integer; stubs, lab: string; can: TCanvas);
    procedure PlotRoom(level, x, y, kind, d, penw: integer; col, bcol: TColor;
      lab, stag, stubs: string; can: TCanvas);
    procedure PlotLink(level, x1, y1, x2, y2, kind, d: integer; col: TColor; lab: string;
      stretched: boolean; can: TCanvas);
    procedure AddLink;
    procedure DeleteLinkExits(map1, map2: integer; x1, y1, x2, y2: integer;
      firstonly: boolean);
    procedure FigureRoomExits(x1, y1, x2, y2: integer; var link1: string; var link2: string);
    procedure AddHistory(str: string);
    procedure FilterStubs(map: string; x, y: integer);
    function CleanRoom(map: string; x, y: integer): integer;
    procedure MoveRoom(map: string; x, y, x2, y2: integer);
    procedure SelectRoom(x, y: integer);
    procedure UpdateSelection;
    procedure MoveSelectedRooms(dir: integer);
    procedure CheckRoomLabel;
    procedure WipeMap;
    function CheckAntiStub(map: string; x, y: integer; antistub: string): integer;
    procedure CheckStub(stub, d1, d2: string; x1, y1, x2, y2: integer;
      antistub, map, stubs: string; table: TClientDataset);
    procedure UpdateExample;
    procedure UpdateCaption;
    procedure UpdateStubs;
    function AssignDistance(map: string; x, y, d: integer): boolean;
    function PopulateSprintCommands(map: string; x, y: integer): boolean;
    procedure StopCharting;
    procedure HaltFailure;
    procedure UpdateBasementButtons;
    procedure ReallyDrawMap;
    function CountMaps: integer;
    procedure CopySprint(a1m2, x, y: integer; grab: string; cmds: TStringList);
    procedure InitializeCanvas(can: TCanvas; w, h, d: integer);

  public
    { Public declarations }
    roommap, exportfile, currentstubs, awaitinglabel, ultimatemap, ss1, ss2: string;
    CMAP, link1, link2, gmap1, gmap2, s1, s2, s3, s4, s5, s6, p1, p2, p3, p4: string;
    overrideroom, dontdrawmap, refreshingroom, awaitingfailure, room1, room2: boolean;
    undoid, xoffset, yoffset, roomkind, roomcolorfg, roomcolorbg: integer;
    CX, CY, ultimatex, ultimatey, x1, y1, x2, y2, r1, r2, r3, r4, r5, r6, lcol: integer;
    procedure DrawMap(drawruler: boolean);
    procedure CreateRoom(map: string; x, y, kind, colfg, colbg: integer;
      slabel, stag, swarn, stubs: string; overwrite: boolean);
    function DeleteRoom(map: string; x, y: integer; dexits, manual: boolean): boolean;
    procedure CreateLinkExits(map1, map2, dir1, dir2, pre1, post1, pre2, post2: string;
      x1, y1, x2, y2, col: integer; dohistory: boolean);
    function ChangeMap(newmap: string): boolean;
    function WarningRoom(map: string; x, y: integer): string;
    function FigureRoom: integer;
    procedure CenterX;
    function IsExit(dir, map: string; x, y: integer): boolean;
    function RoomProperties(map: string; x, y: integer;
      var r1, r2, r3: integer; var s1, s2, s3, ss1: string): boolean;
    procedure UpdateFilters(const map: string);
    function RoomAt(map: string; x, y: integer): boolean;
    function ExportMap(map, savefile: string): boolean;
    procedure UpdateCharting(which: integer; dontshow: boolean);
    procedure CollectStubs(str: string);
    function Sprint(map: string; x, y: integer; docopy: boolean;
      var commands: TStringList): integer;
    procedure SetXY(map: string; x, y: integer);
    procedure AbortChart;
    procedure FinalizeLink(forced: boolean);
    function IsCardinal(dir: string): boolean;
    function TryCurrentExit(dir: string): boolean;
    function MoveXY(dir: string): boolean;
    function OppositeDir(dir: string): string;
    procedure CheckBasement(map1, map2, link1, link2: string);
    function FigureAB(const map, dir: string): string;

    function GetRoomID(const mapname: string; x, y: integer): integer;
    function GetMapID(const mapname: string): integer;
    function GetMapName(mapid: integer): string;
    function GetNewID: integer;
  end;

var
  FormMap: TFormMap;

implementation

uses Main, Variants;

{$R *.DFM}

var
  undomax, FEX, BEX, GX, GY, SX, SY, GAX, GAY, grooms, glinks: integer;
  sprinttox, sprinttoy: integer;
  undoing, suppressdraw, bypasscloseup, stretching, printing, doruler: boolean;
  stretched, controlling, activating, highlightstubs, iscreated, drawingmap: boolean;
  roomlabel, roomtag, roomwarn, roomstubs, mapundostr: string;
  Undos, sprintcoms, checkedxy, nextmap, nextx, nexty, currentmap, currentx, currenty, ds: TStringList;
  sprinttomap: string;

procedure TFormMap.FormCreate(Sender: TObject);
var didfail: boolean;
begin
  iscreated:=false;
  awaitinglabel:='';
  doruler:=false;
  refreshingroom:=false;
  awaitingfailure:=false;
  drawingmap:=false;
  currentstubs:='';
  Undos:=TStringList.create;
  sprintcoms:=TStringList.create;
  highlightstubs:=false;
  dontdrawmap:=false;
  activating:=false;
  undomax:=200;
  undoid:=0;
  undoing:=false;
  roomkind:=0;
  roomcolorfg:=0;
  roomcolorbg:=0;
  roomlabel:='';
  roomtag:='';
  roomwarn:='';
  roomstubs:='';
  mapundostr:='';
  overrideroom:=false;
  suppressdraw:=false;
  bypasscloseup:=false;
  stretching:=false;
  stretched:=false;
  controlling:=false;
  xoffset:=0; yoffset:=0;
  GX:=0; GY:=0;
  SX:=0; SY:=0;
  GAX:=0; GAY:=0;
  grooms:=0;
  glinks:=0;
  ColorGrid.foregroundindex:=0;
  ColorGrid.backgroundindex:=15;
  FEX:=clblack;
  BEX:=clwhite;
  printing:=false;
  WipeMap;
  didfail:=false;
  FMapDic := TIDDic.Create;
  FRoomDic := TIDDic.Create;
  ListMapsDropDown(self);
  dsCurrentMap.CloneCursor(Datawell.TableMaps, false);
  dsCurrentMap.Filtered := true;
  dsCurrentMap.Filter := format('map = %s', [quotedStr(Datawell.TableMaps.fieldbyname('Map').asstring)]);
  if Datawell.TableGlobals.fieldbyname('ChartLastMap').asstring = '' then
    didfail:=true
  else if not ChangeMap(Datawell.TableGlobals.fieldbyname('ChartLastMap').asstring) then
    didfail:=true;

  if didfail then begin
    Datawell.TableMaps.first;
    Datawell.TableGlobals.edit;
    Datawell.TableGlobals.fieldbyname('ChartLastMap').asstring:=
      Datawell.TableMaps.fieldbyname('Map').asstring;
    Datawell.TableGlobals.post;
    ChangeMap(Datawell.TableGlobals.fieldbyname('ChartLastMap').asstring);
  end;
  UpdateStubs;
  SetXY(ListMaps.text,0,0);
  LabelClock.caption:=inttostr(Datawell.TableGlobals.fieldbyname('FailureTimer').asinteger);
  iscreated:=true;
end;

procedure TFormMap.FormDestroy(Sender: TObject);
begin
  FMapDic.Free;
  FRoomDic.Free;
  Undos.free;
  sprintcoms.free;
end;

procedure TFormMap.FormShow(Sender: TObject);
var
   mapname: string;
begin
  mapname := dsCurrentMap.FieldByName('Map').AsString;
  if mapname = '' then
   Exit;

  UpdateFilters(mapname);
  activating:=true;
  TimerActivating.enabled:=true;
  ListMapsDropDown(self);
  DrawMap(false);
end;

procedure TFormMap.WMGetMinMaxInfo(var MSG: Tmessage);
begin
  inherited;
  with PMinMaxInfo(MSG.lparam)^ do begin
    with ptMinTrackSize do begin
      X:=339;
      Y:=186;
    end;
    with ptMaxTrackSize do begin
      X:=9999;
      Y:=9999;
    end;
  end;
end;

procedure TFormMap.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  LabelStatus.caption:=' '+(sender as TControl).hint;
end;

procedure TFormMap.BtnHelpClick(Sender: TObject);
begin
  Datawell.Help('cartographer');
end;

procedure TFormMap.MenuStayOnTopClick(Sender: TObject);
begin
  MenuStayOnTop.checked:=not MenuStayOnTop.checked;
  PopupStayOnTop.checked:=MenuStayOnTop.checked;
  if MenuStayOnTop.checked = true then formstyle:=fsstayontop
  else formstyle:=fsnormal;
end;

procedure TFormMap.MapCanvasMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  d: integer;
  xs, ys: string;
begin
  if (x = FLastMouseLoc.X) and (y = FLastMouseLoc.y) then
    Exit
  else FLastMouseLoc := point(x, y);
  GAX:=x; GAY:=y;
  if ListMaps.text = '' then begin
    LabelStatus.color:=clbtnface;
    LabelStatus.font.color:=clblack;
    LabelStatus.caption:='';
    Ruler1Pointer.left:=GAX+13;
    Ruler1Pointer.show;
    Ruler1Pointer2.hide;
    Ruler2Pointer.top:=GAY-4;
    Ruler2Pointer.show;
    Ruler2Pointer2.hide;
    exit;
  end;
  if not active then exit;
  d:=dsCurrentMap.fieldbyname('Zoom').asinteger div 10;
  Ruler1Pointer.left:=GAX-(GAX mod d)+(d div 2)+11;
  Ruler1Pointer2.left:=Ruler1Pointer.left;
  Ruler2Pointer.top:=GAY-(GAY mod d)+(d div 2)-6;
  Ruler2Pointer2.top:=Ruler2Pointer.top;
  x:=x div d;
  y:=y div d;
  dec(x,xoffset);
  dec(y,yoffset);
  y:=-y;
  MapCanvas.cursor:=crdefault;
  if ((x mod 2) = 0) and ((y mod 2) = 0) then begin
    GX:=x div 2;
    GY:=y div 2;
    xs:=inttostr(GX);
    ys:=inttostr(GY);
    LabelPosition.font.color:=clblack;
    MapCanvas.cursor:=cruparrow;
    Ruler1Pointer.hide;
    Ruler2Pointer.hide;
    Ruler1Pointer2.show;
    Ruler2Pointer2.show;
    CheckRoomLabel;
  end
  else begin
    LabelStatus.color:=clbtnface;
    LabelStatus.font.color:=clblack;
    LabelStatus.caption:=' '+(sender as TControl).hint;
    xs:=floattostr(x/2);
    ys:=floattostr(y/2);
    LabelPosition.font.color:=clgray;
    Ruler1Pointer.show;
    Ruler2Pointer.show;
    Ruler1Pointer2.hide;
    Ruler2Pointer2.hide;
  end;
  LabelPosition.caption:='('+xs+','+ys+')';
  if stretching then begin
    if LabelPosition.font.color = clblack then begin
      DrawMap(false);
    end;
    if (GX <> SX) or (GY <> SY) then stretched:=true;
  end;
end;

procedure TFormMap.MapCanvasMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if activating then exit;
  if listMaps.text = '' then exit;
  if not BtnEdit.down then exit;
  if button <> mbleft then exit;
  if LabelPosition.font.color = clgray then exit;
  stretching:=true;
  if shift >= [ssctrl] then controlling:=true;
  SX:=GX; SY:=GY;
end;

procedure TFormMap.MapCanvasMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if activating then exit;
  if listMaps.text = '' then exit;
  if not BtnEdit.down then exit;
  if not stretching then exit;
  if button <> mbleft then exit;
  if (controlling) and (RoomAt(dsCurrentMap.fieldbyname('Map').asstring,GX,GY)) and
    (not stretched) and (SX = GX) and (SY = GY) then begin
    controlling:=false;
    stretching:=false;
    SelectRoom(SX,SY);
    DrawMap(false);
    exit;
  end;
  AddLink;
end;

procedure TFormMap.AddLink;
const
  ANY_LINK = '(Map1 = %d) and (((x = %d) and (y = %d) and (x2 = %d) and (y2 = %d)) ' +
             'or ((x = %d) and (y = %d) and (x2 = %d) and (y2 = %d)))';
var
  didfind, linked, doroom1, doroom2: boolean;
  link1, link2, map1, map2, s1, s2, s3, s4, s5, s6, p1, p2, p3, p4, ss1, ss2: string;
  form: TFormConfirmLink;
  r1, r2, r3, r4, r5, r6, rx1, ry1, rx2, ry2, lcol, p: integer;
  table: TClientDataset;
begin
  stretching:=false;
  //Moving a room
  if CleanRoom(dsCurrentMap.fieldbyname('Map').asstring,GX,GY) < 1 then begin
    if Datawell.selx.count > 0 then begin
      p:=Datawell.selx.indexof(inttostr(SX)+'|'+inttostr(SY));
      if p >= 0 then begin
        stretched:=false;
        if (SX = GX) and (SY = GY) then exit;
        inc(undoid);
        MoveRoom(dsCurrentMap.fieldbyname('Map').asstring,SX,SY,GX,GY);
        if Datawell.selx.count = 1 then MenuClearSelections.click;
        DrawMap(false);
        exit;
      end;
    end;
  end;
  map1:=dsCurrentMap.fieldbyname('Map').asstring;
  map2:=map1;
  //Adding a room
  if (SX = GX) and (SY = GY) then begin
    if stretched then begin
      stretched:=false;
      DrawMap(false);
      exit;
    end;
    stretched:=false;
    if FigureRoom < 0 then exit;
    if dsCurrentMap.fieldbyname('Map').asstring = '' then exit;
    if (LabelPosition.font.color = clgray) and (not overrideroom) then exit;
    if overrideroom then begin
      CreateRoom(roommap,GX,GY,roomkind,roomcolorfg,roomcolorbg,
        roomlabel,roomtag,roomwarn,roomstubs,true);
    end
    else begin
      inc(undoid);
      CreateRoom(dsCurrentMap.fieldbyname('Map').asstring,GX,GY,FigureRoom,
        Datawell.FigureColor(ColorGrid.ForegroundIndex),
        Datawell.FigureColor(ColorGrid.BackgroundIndex),'','','','',false);
    end;
    DrawMap(false);
    UpdateFilters(map1);
    exit;
  end;
  stretched:=false;
  table:=TClientDataset.create(self);
  with table do begin
    CloneCursor(Datawell.TableExits, false);
    filter := format(ANY_LINK, [dsCurrentMap.fieldbyname('Map_id').asInteger, SX, SY, GX, GY, GX, GY, SX, SY]);
    filtered := true;
    active:=true;
    first;
    begin
      didfind:= not EOF;
      if didfind then begin //Delete the link
        inc(undoid);
        DeleteLinkExits(dsCurrentMap.fieldbyname('Map_id').asInteger,
          dsCurrentMap.fieldbyname('Map_id').asInteger,SX,SY,GX,GY,false);
      end
      else begin //Add the link
        controlling:=false;
        rx1:=SX; ry1:=SY; rx2:=GX; ry2:=GY;
        if dsCurrentMap.fieldbyname('Map').asstring <> '' then begin
          doroom1:=true; doroom2:=true;
          link1:=''; link2:='';
          FigureRoomExits(rx1,ry1,rx2,ry2,link1,link2);
          lcol:=Datawell.FigureColor(ColorGrid.foregroundindex);
          r1:=FigureRoom;
          r2:=Datawell.FigureColor(ColorGrid.foregroundindex);
          r3:=Datawell.FigureColor(ColorGrid.backgroundindex);
          s1:=''; s2:=''; s3:=''; ss1:='';
          RoomProperties(map1,rx1,ry1,r1,r2,r3,s1,s2,s3,ss1);
          r4:=FigureRoom;
          r5:=Datawell.FigureColor(ColorGrid.foregroundindex);
          r6:=Datawell.FigureColor(ColorGrid.backgroundindex);
          s4:=''; s5:=''; s6:=''; ss2:='';
          RoomProperties(map2,rx2,ry2,r4,r5,r6,s4,s5,s6,ss2);
          p1:=''; p2:=''; p3:=''; p4:='';
          linked:=true;
          if not dsCurrentMap.fieldbyname('SpeedEdit').asboolean then begin
            form:=TFormConfirmLink.create(self);
            case r1 of
              0: form.b0.down:=true;
              1: form.b1.down:=true;
              2: form.b2.down:=true;
              3: form.b3.down:=true;
              4: form.b4.down:=true;
            end;
            case r4 of
              0: form.c0.down:=true;
              1: form.c1.down:=true;
              2: form.c2.down:=true;
              3: form.c3.down:=true;
              4: form.c4.down:=true;
            end;
            form.ColorLink.foregroundindex:=ColorGrid.foregroundindex;
            form.Colors1.foregroundindex:=Datawell.ColorFigure(r2);
            form.Colors1.backgroundindex:=Datawell.ColorFigure(r3);
            form.Colors2.foregroundindex:=Datawell.ColorFigure(r5);
            form.Colors2.backgroundindex:=Datawell.ColorFigure(r6);
            form.Edit1.text:=link1;
            form.Edit2.text:=link2;
            form.EditMap1.itemIndex:=form.EditMap1.Items.indexOf(map1);
            form.X1.value:=rx1;
            form.Y1.value:=ry1;
            form.EditMap2.itemIndex:=form.EditMap2.Items.indexOf(map2);
            form.X2.value:=rx2;
            form.Y2.value:=ry2;
            form.EditPre1.text:=p1;
            form.EditPost1.text:=p2;
            form.EditPre2.text:=p3;
            form.EditPost2.text:=p4;
            form.UpdateEnabling;
            form.showmodal;
            map1:=form.EditMap1.text;
            map2:=form.EditMap2.text;
            link1:=form.Edit1.text;
            link2:=form.Edit2.text;
            p1:=form.EditPre1.text;
            p2:=form.EditPost1.text;
            p3:=form.EditPre2.text;
            p4:=form.EditPost2.text;
            if form.CheckO.enabled then begin
              doroom1:=form.CheckO.checked;
              r1:=form.FigureRoom(1);
              r2:=Datawell.FigureColor(form.Colors1.ForegroundIndex);
              r3:=Datawell.FigureColor(form.Colors1.BackgroundIndex);
            end;
            if form.CheckD.enabled then begin
              doroom2:=form.CheckD.checked;
              r4:=form.FigureRoom(2);
              r5:=Datawell.FigureColor(form.Colors2.ForegroundIndex);
              r6:=Datawell.FigureColor(form.Colors2.BackgroundIndex);
            end;
            lcol:=Datawell.FigureColor(form.ColorLink.foregroundindex);
            rx1:=trunc(form.X1.value);
            ry1:=trunc(form.Y1.value);
            rx2:=trunc(form.X2.value);
            ry2:=trunc(form.Y2.value);
            linked:=form.linked;
            form.free;
          end;
          if linked then begin
            inc(undoid);
            //Add the rooms (if necessary)
            if doroom1 then begin
              SX:=rx1;
              SY:=ry1;
              GX:=rx1;
              GY:=ry1;
              roommap:=map1;
              roomkind:=r1;
              roomcolorfg:=r2;
              roomcolorbg:=r3;
              roomlabel:=s1;
              roomtag:=s2;
              roomwarn:=s3;
              roomstubs:=ss1;
              overrideroom:=true;
              AddLink;
              overrideroom:=false;
            end;
            if doroom2 then begin
              SX:=rx2;
              SY:=ry2;
              GX:=rx2;
              GY:=ry2;
              roommap:=map2;
              roomkind:=r4;
              roomcolorfg:=r5;
              roomcolorbg:=r6;
              roomlabel:=s4;
              roomtag:=s5;
              roomwarn:=s6;
              roomstubs:=ss2;
              overrideroom:=true;
              AddLink;
              overrideroom:=false;
            end;
            //Create the link
            CreateLinkExits(map1,map2,link1,link2,p1,p2,p3,p4,rx1,ry1,rx2,ry2,lcol,true);
            //Check for attic/basement
            CheckBasement(map1,map2,link1,link2);
          end;
        end;
      end;
    end;
  end;
  table.free;
  DrawMap(false);
end;

procedure TFormMap.BtnCClick(Sender: TObject);
var amt, which: integer;
begin
  if listMaps.text = '' then exit;
  if Btnx4.down then amt:=8
  else if Btnx8.down then amt:=16
  else amt:=2;
  which:=TBitBtn(sender).tag;
  case which of
    0: begin
         xoffset:=-dsCurrentMap.fieldbyname('HomeX').asinteger*2;
         yoffset:=dsCurrentMap.fieldbyname('HomeY').asinteger*2;
       end;
    1: inc(yoffset,amt);
    2: dec(xoffset,amt);
    3: dec(yoffset,amt);
    4: inc(xoffset,amt);
    5: begin dec(xoffset,amt); inc(yoffset,amt); end;
    6: begin dec(xoffset,amt); dec(yoffset,amt); end;
    7: begin inc(xoffset,amt); dec(yoffset,amt); end;
    8: begin inc(xoffset,amt); inc(yoffset,amt); end;
  end;
  DrawMap(true);
end;

procedure TFormMap.MenuExitClick(Sender: TObject);
begin
  close;
end;

procedure TFormMap.MenuCtrlTabClick(Sender: TObject);
begin
  if TMenuItem(sender).tag = 0 then FormMain.NextScreen(12)
  else FormMain.PriorScreen(12);
end;

procedure TFormMap.MenuEscapeClick(Sender: TObject);
begin
  if PanelSelection.visible then begin
    MenuClearSelections.Click;
    exit;
  end;
  if ListMaps.droppeddown then begin
    bypasscloseup:=true;
    ListMaps.setfocus;
    bypasscloseup:=false;
    exit;
  end;
  FormMain.show;
end;

procedure TFormMap.MenuPanicClick(Sender: TObject);
begin
  if Datawell.selx.count > 0 then MenuClearSelections.click;
  FormMain.MenuPanic.click;
end;

procedure TFormMap.MenuAutotyperClick(Sender: TObject);
begin
  FormMain.OpenFloater(TComponent(sender).tag);
end;

procedure TFormMap.PopupMenuPopup(Sender: TObject);
var
  didfind: boolean;
  x, y, d: integer;
begin
  if listMaps.text = '' then exit;
  didfind:=false;
  d:=dsCurrentMap.fieldbyname('Zoom').asinteger div 10;
  x:=GAX div d;
  y:=GAY div d;
  dec(x,xoffset);
  dec(y,yoffset);
  y:=-y;
  if ((x mod 2) = 0) and ((y mod 2) = 0) then begin
    x:=x div 2;
    y:=y div 2;
    with Datawell.TableRooms do begin
      first;
      while not EOF do begin
        if (Datawell.TableRooms.fieldbyname('X').asinteger = x) and
          (Datawell.TableRooms.fieldbyname('Y').asinteger = y) then begin
          didfind:=true;
          break;
        end;
        next;
      end;
    end;
    PopupSetLocation.caption:='Set ('+inttostr(x)+','+inttostr(y)+
      ') as &Current Location  [F3]';
    PopupSetLocation.enabled:=true;
  end
  else begin
    PopupSetLocation.caption:='Set Position as &Current Location  [F3]';
    PopupSetLocation.enabled:=false;
  end;
  if didfind then begin
    PopupProperties.caption:='Room &Properties ('+inttostr(x)+','+
      inttostr(y)+')  [F2]';
    PopupProperties.enabled:=true;
    PopupDelete.caption:='&Delete Room ('+inttostr(x)+','+
      inttostr(y)+')  [Del]';
    PopupDelete.enabled:=true;
    PopupDelete2.caption:='&Delete Room and Exits ('+inttostr(x)+','+
      inttostr(y)+')  [Ctrl+Del]';
    PopupDelete2.enabled:=true;
    PopupSprint.caption:='&Sprint to Room ('+inttostr(x)+','+
      inttostr(y)+')  [F4]';
    PopupSprint.enabled:=true;
    PopupCopySprint.caption:='Cop&y Sprint to Room ('+inttostr(x)+','+
      inttostr(y)+') Results into Alias or Macro';
    PopupCopySprint.enabled:=true;
    PopupSelect.caption:='Se&lect/Deselect Room ('+inttostr(x)+','+
      inttostr(y)+')  [F12]';
    PopupSelect.enabled:=true;
  end
  else begin
    PopupProperties.caption:='Room &Properties  [F2]';
    PopupProperties.enabled:=false;
    PopupDelete.caption:='&Delete Room  [Del]';
    PopupDelete.enabled:=false;
    PopupDelete.caption:='&Delete Room and Exits [Ctrl+Del]';
    PopupDelete.enabled:=false;
    PopupSprint.caption:='&Sprint to a Room  [F4]';
    PopupSprint.enabled:=false;
    PopupCopySprint.caption:='Cop&y Sprint Results to Alias or Macro';
    PopupCopySprint.enabled:=false;
    PopupSelect.caption:='Se&lect/Deselect Room  [F12]';
    PopupSelect.enabled:=false;
  end;
  if PanelSelection.visible then begin
    PopupProperties.enabled:=false;
    PopupSetLocation.enabled:=false;
    PopupSprint.enabled:=false;
    PopupDelete.enabled:=false;
    PopupDelete2.enabled:=false;
  end;
  MenuBasement.enabled:=(dsCurrentMap.fieldbyname('S2').asstring <> '');
  MenuAttic.enabled:=(dsCurrentMap.fieldbyname('S3').asstring <> '');
end;

procedure TFormMap.PopupDeleteClick(Sender: TObject);
begin
  inc(undoid);
  DeleteRoom(dsCurrentMap.fieldbyname('Map').asstring,GX,GY,true,true);
  DrawMap(false);
end;

procedure TFormMap.BtnPrintClick(Sender: TObject);
var PF: System.text;
begin
  if not Datawell.CheckRinged(true,'map printing') then exit;
  if PanelSelection.visible then exit;
  if listMaps.text = '' then exit;
  if dsCurrentMap.fieldbyname('Map').asstring = '' then exit;
  if not DialogPrint.execute then exit;
  Printer.orientation:=polandscape;
  AssignPRN(PF);
  ReWrite(PF);
  Printer.title:=Datawell.ptitle;
  printing:=true;
  ReallyDrawMap;
  printing:=false;
  CloseFile(PF);
end;

procedure TFormMap.PopupPropertiesClick(Sender: TObject);
var
  form: TFormRProperties;
  mapname: string;
begin
  if highlightstubs then exit;
  form:=TFormRProperties.create(self);
  mapname := dsCurrentMap.fieldbyname('Map').asstring;
  if form.LoadOptions(mapname,GX,GY,false) then
    form.showmodal
  else begin
    form.free;
    messagedlg('No room at ('+inttostr(GX)+','+inttostr(GY)+')',mtinformation,[mbok],0);
    exit;
  end;
  UpdateFilters(mapname);
  form.free;
  DrawMap(false);
end;

procedure TFormMap.MenuNewMapClick(Sender: TObject);
var
  form: TFormNewMap;
  didcreate: boolean;
  mapname: string;
  maps: integer;
begin
  if PanelSelection.visible then exit;
  maps:=CountMaps;
  if maps >= 10 then begin
    if not Datawell.CheckRinged(true,'more than 10 maps') then exit;
  end;
  if (BtnChart.down) or (BtnFollow.down) then begin
    messagedlg('You cannot create a new map level here while charting or following',
      mtinformation,[mbok],0);
    exit;
  end;
  form:=TFormNewMap.create(self);
  form.showmodal;
  didcreate:=form.didcreate;
  mapname:=form.mapname;
  form.free;
  if not didcreate then exit;
  BtnEdit.down:=true;
  ChangeMap(mapname);
  BtnHome.click;
  if Datawell.TableGlobals.fieldbyname('ChartLastMap').asstring = '' then
  begin
    Datawell.TableGlobals.edit;
    Datawell.TableGlobals.fieldbyname('ChartLastMap').asstring:=mapname;
    Datawell.TableGlobals.post;
    Datawell.TableGlobals.ApplyUpdates(1);
  end;
  //If first map, set the current location to it
  if maps = 0 then begin
    CX:=0;
    CY:=0;
    CMAP:=mapname;
  end;
end;

procedure TFormMap.MenuExportClick(Sender: TObject);
begin
  if listMaps.text = '' then exit;
  DialogSave.initialdir:=Datawell.GetDir(3);
  DialogSave.filename:=dsCurrentMap.fieldbyname('Map').asstring;
  if not DialogSave.execute then exit;
  Datawell.SetDir(3,DialogSave.filename);
  if ExportMap(dsCurrentMap.fieldbyname('Map').asstring,DialogSave.filename) then
    messagedlg('Map level ['+dsCurrentMap.fieldbyname('Map').asstring+'] exported successfully',
      mtinformation,[mbok],0);
end;

procedure TFormMap.MenuBatchExportClick(Sender: TObject);
var
  table: TClientDataset;
  success: boolean;
begin
//Change this procedure and you need to change the ExportMap procedure
  if not Datawell.CheckRinged(true,'batch map exporting') then exit;
  if listMaps.text = '' then exit;
  DialogSave.initialdir:=Datawell.GetDir(3);
  DialogSave.filename:='BATCHEXPORT';
  if not DialogSave.execute then exit;
  Datawell.SetDir(3,DialogSave.filename);
  if messagedlg('This will overwrite any same-named maps.  Continue?',
    mtwarning,[mbok,mbcancel],0) = mrcancel then exit;
  success:=true;
  table:=TClientDataset.create(self);
  with table do begin
    CloneCursor(Datawell.TableMaps, false);
    active:=true;
    first;
    while not EOF do begin
      if not ExportMap(fieldbyname('Map').asstring,
        fieldbyname('Map').asstring+'.tkm') then success:=false;
      next;
    end;
  end;
  table.free;
  if success then messagedlg('Maps batch-exported successfully',
    mtinformation,[mbok],0);
end;

function TFormMap.ExportMap(map, savefile: string): boolean;
var
  adds, memos: TStringList;
  work: string;
  table: TClientDataset;
  didfind: boolean;
begin
  result:=false;
  table:=TClientDataset.create(self);
  try
    table.CloneCursor(Datawell.TableMaps, false);
    didFind := table.Locate('map', map, []);
    if not didfind then
      exit;
    adds:=TStringList.create;
    memos:=TStringList.create;
    try
      with table do begin
        adds.add('DO NOT EDIT THIS FILE!');
        adds.add(Datawell.version2);
        adds.add(map);
        adds.add(inttostr(fieldbyname('Zoom').asinteger));
        work:='FALSE'; if fieldbyname('ShowGrid').asboolean then work:='TRUE';
          adds.add(work);
        work:='FALSE'; if fieldbyname('ShowLabels').asboolean then work:='TRUE';
          adds.add(work);
        work:='FALSE'; if fieldbyname('SpeedEdit').asboolean then work:='TRUE';
          adds.add(work);
        adds.add(inttostr(fieldbyname('HomeX').asinteger));
        adds.add(inttostr(fieldbyname('HomeY').asinteger));
        // 4.600 - Added: LL, FColor, BColor, LColor, SColor, S1-5, I1-5, L1-5
        //                ShowTags, ShowShadows, ShowStubs
        work:='FALSE'; if fieldbyname('LL').asboolean then work:='TRUE';
          adds.add(work);
        adds.add(inttostr(fieldbyname('FColor').asinteger));
        adds.add(inttostr(fieldbyname('BColor').asinteger));
        adds.add(inttostr(fieldbyname('LColor').asinteger));
        adds.add(inttostr(fieldbyname('SColor').asinteger));
        adds.add(fieldbyname('S1').asstring);
        adds.add(fieldbyname('S2').asstring);
        adds.add(fieldbyname('S3').asstring);
        adds.add(fieldbyname('S4').asstring);
        adds.add(fieldbyname('S5').asstring);
        adds.add(inttostr(fieldbyname('I1').asinteger));
        adds.add(inttostr(fieldbyname('I2').asinteger));
        adds.add(inttostr(fieldbyname('I3').asinteger));
        adds.add(inttostr(fieldbyname('I4').asinteger));
        adds.add(inttostr(fieldbyname('I5').asinteger));
        work:='FALSE'; if fieldbyname('L1').asboolean then work:='TRUE';
          adds.add(work);
        work:='FALSE'; if fieldbyname('L2').asboolean then work:='TRUE';
          adds.add(work);
        work:='FALSE'; if fieldbyname('L3').asboolean then work:='TRUE';
          adds.add(work);
        work:='FALSE'; if fieldbyname('L4').asboolean then work:='TRUE';
          adds.add(work);
        work:='FALSE'; if fieldbyname('L5').asboolean then work:='TRUE';
          adds.add(work);
        work:='FALSE'; if fieldbyname('ShowTags').asboolean then work:='TRUE';
          adds.add(work);
        work:='FALSE'; if fieldbyname('ShowShadows').asboolean then work:='TRUE';
          adds.add(work);
        work:='FALSE'; if fieldbyname('ShowStubs').asboolean then work:='TRUE';
          adds.add(work);
        memos.assign(fieldbyname('Memo'));
        adds.AddStrings(memos);
        adds.add('--------ROOMS');
        active:=false;
        CloneCursor(Datawell.TableRooms, false);
        filter := format('(Map_id = %d)', [getMapID(map)]);
        filtered := true;
        active:=true;
        first;
        while not EOF do begin
          adds.add(fieldbyname('X').AsString);
          adds.add(fieldbyname('Y').AsString);
          adds.add(fieldbyname('Kind').AsString);
          adds.add(fieldbyname('Color').AsString);
          adds.add(fieldbyname('BColor').AsString);
          adds.add(fieldbyname('Label').asstring);
          adds.add(fieldbyname('Tag').asstring);
          adds.add(fieldbyname('Warn').asstring);
          // 4.500 - Added: Stubs, S1
          adds.add(fieldbyname('Stubs').asstring);
          adds.add(fieldbyname('S1').asstring);
          next;
        end;
        adds.add('--------EXITS');
        active:=false;
        CloneCursor(Datawell.TableExits, false);
        filter := format('(Map1 = %d)', [getMapID(map)]);
        filtered := true;
        active:=true;
        first;
        while not EOF do begin
          adds.add(fieldbyname('X').AsString);
          adds.add(fieldbyname('Y').AsString);
          adds.add(fieldbyname('FExit').asstring);
          adds.add(Datawell.TableMaps.Lookup('map_id', fieldbyname('Map2').value, 'map'));
          adds.add(fieldbyname('X2').AsString);
          adds.add(fieldbyname('Y2').AsString);
          adds.add(fieldbyname('PreAction').asstring);
          adds.add(fieldbyname('PostAction').asstring);
          adds.add(fieldbyname('Color').AsString);
          adds.add(fieldbyname('FOrder').AsString);
          next;
        end;
        adds.add('--------MEMOS');
        active:=false;
        CloneCursor(Datawell.TableRooms, false);
        filter := format('(map_id = %d) and (memo is not null)', [getMapID(map)]);
        filtered := true;
        active:=true;
        first;
        while not EOF do begin
          adds.add(inttostr(fieldbyname('X').asinteger));
          adds.add(inttostr(fieldbyname('Y').asinteger));
          memos.clear;
          memos.assign(fieldbyname('Memo'));
          adds.AddStrings(memos);
          adds.add('-|-|-|-|');
          next;
        end;
      end;
      adds.add('--------EOF');
      adds.SaveToFile(savefile);
    finally
      adds.free;
      memos.free;
    end;
  finally
    table.free;
  end;
  result:=true;
end;

procedure TFormMap.MenuImportClick(Sender: TObject);
var
  cver, nmap, omap, dMap, importfile, sfileversion, version: string;
  i, fileversion, currentversion: integer;
  linked, bwork: boolean;
  form: TFormMapName;
  form2: TFormSaving;
  lines, memos: TStringList;
  x, y, x2, y2: integer;
begin
  Abort; //not currently supported

  if CountMaps >= 10 then begin
    if not Datawell.CheckRinged(true,'more than 10 maps') then exit;
  end;
  if (BtnChart.down) or (BtnFollow.down) then begin
    messagedlg('You cannot import any map levels while actively charting or following',
      mtinformation,[mbok],0);
    exit;
  end;
  DialogOpen.initialdir:=Datawell.GetDir(3);
  if not DialogOpen.execute then exit;
  Datawell.SetDir(3,DialogOpen.files[0]);
  if DialogOpen.files.count > 1 then begin
    if not Datawell.CheckRinged(true,'batch map importing') then exit;
  end;
  for i:=0 to DialogOpen.files.count-1 do begin
    importfile:=lowercase(DialogOpen.files[i]);
    nmap:=extractfilename(importfile);
    if extractfileext(nmap) <> '' then delete(nmap,length(nmap)-3,9999);
    nmap:=uppercase(nmap);
    if not FileExists(importfile) then begin
      messagedlg('File does not exist: '+importfile,mtinformation,[mbok],0);
      continue;
    end;
    lines:=TStringList.create;
    try
      lines.loadfromfile(importfile);
      if lines.count < 1 then
        Continue;
      lines.delete(0);
      sfileversion:=lines[0];
      lines.delete(0);
      omap:=lines[0];
    finally
      lines.free;
    end;
    version:=sfileversion;
    version:=Datawell.subs(FormatSettings.DecimalSeparator,'',version);
    version:=Datawell.subs('.','',version);
    version:=Datawell.subs(',','',version);
    fileversion:=StrToIntDef(version, -1);
    if fileversion = -1 then begin
      messagedlg('Corrpupt .TKM file: '+importfile,mtinformation,[mbok],0);
      continue;
    end;
    if fileversion < 4400 then begin
      messagedlg('Unsupported .TKM file: '+importfile,mtinformation,[mbok],0);
      continue;
    end;
    if (fileversion > 6300) and (fileversion < 7600) then begin
      messagedlg('Beta files can''t be imported',mtinformation,[mbok],0);
      exit;
    end;
    cver:=Datawell.version;
    cver:=Datawell.subs(FormatSettings.DecimalSeparator,'',cver);
    cver:=Datawell.subs('.','',cver);
    cver:=Datawell.subs(',','',cver);
    currentversion:=strtoint(cver);
    while copy(sfileversion,length(sfileversion),1) = '0' do
      sfileversion:=copy(sfileversion,1,length(sfileversion)-1);
    if fileversion > currentversion then begin
      messagedlg('You need client version '+sfileversion+' to import file: '+importfile,
        mtinformation,[mbok],0);
      continue;
    end;
    form:=TFormMapName.create(self);
    form.EditMap.text:=nmap;
    form.showmodal;
    linked:=form.linked;
    nmap:=form.EditMap.text;
    form.free;
    if not linked then continue;
    form2:=TFormSaving.create(self);
    try
      form2.Label1.caption:='Importing Map Level...';
      form2.Label1.width:=165;
      form2.Label1.alignment:=taCenter;
      form2.ImageDisk.hide;
      form2.show;
      form2.bringtofront;
      form2.refresh;
      lines:=TStringList.create;
      memos:=TStringList.create;
      try
        if FileExists(importFile) then
          lines.loadfromfile(importfile);
        lines.delete(0);
        lines.delete(0);
        lines.delete(0);
        //Map values
        Datawell.TableMaps.append;
        Datawell.TableMaps.fieldbyname('Map').asstring:=nmap;
        Datawell.TableMaps.fieldbyname('Map_id').asinteger:=GetNewId;
        Datawell.TableMaps.fieldbyname('Zoom').asinteger:=Datawell.toint(lines[0]);
        lines.delete(0);
        if lines[0] = 'TRUE' then bwork:=true else bwork:=false;
        Datawell.TableMaps.fieldbyname('ShowGrid').asboolean:=bwork;
        lines.delete(0);
        if lines[0] = 'TRUE' then bwork:=true else bwork:=false;
        Datawell.TableMaps.fieldbyname('ShowLabels').asboolean:=bwork;
        lines.delete(0);
        if lines[0] = 'TRUE' then bwork:=true else bwork:=false;
        Datawell.TableMaps.fieldbyname('SpeedEdit').asboolean:=bwork;
        lines.delete(0);
        Datawell.TableMaps.fieldbyname('HomeX').asinteger:=Datawell.toint(lines[0]);
        lines.delete(0);
        // 4.500 - HomeY was reversed
        if fileversion >= 4500 then
          Datawell.TableMaps.fieldbyname('HomeY').asinteger:=Datawell.toint(lines[0])
        else Datawell.TableMaps.fieldbyname('HomeY').asinteger:=-Datawell.toint(lines[0]);
        // 4.600 - Added: LL, FColor, BColor, LColor, SColor, S1-5, I1-5, L1-5
        //                ShowTags, ShowShadows, ShowStubs
        if fileversion >= 4600 then begin
          lines.delete(0);
          if lines[0] = 'TRUE' then bwork:=true else bwork:=false;
          Datawell.TableMaps.fieldbyname('LL').asboolean:=bwork;
          lines.delete(0);
          Datawell.TableMaps.fieldbyname('FColor').asinteger:=Datawell.toint(lines[0]);
          lines.delete(0);
          Datawell.TableMaps.fieldbyname('BColor').asinteger:=Datawell.toint(lines[0]);
          lines.delete(0);
          Datawell.TableMaps.fieldbyname('LColor').asinteger:=Datawell.toint(lines[0]);
          lines.delete(0);
          Datawell.TableMaps.fieldbyname('SColor').asinteger:=Datawell.toint(lines[0]);
          lines.delete(0);
          Datawell.TableMaps.fieldbyname('S1').asstring:=lines[0];
          lines.delete(0);
          Datawell.TableMaps.fieldbyname('S2').asstring:=lines[0];
          lines.delete(0);
          Datawell.TableMaps.fieldbyname('S3').asstring:=lines[0];
          lines.delete(0);
          Datawell.TableMaps.fieldbyname('S4').asstring:=lines[0];
          lines.delete(0);
          Datawell.TableMaps.fieldbyname('S5').asstring:=lines[0];
          lines.delete(0);
          Datawell.TableMaps.fieldbyname('I1').asinteger:=Datawell.toint(lines[0]);
          lines.delete(0);
          Datawell.TableMaps.fieldbyname('I2').asinteger:=Datawell.toint(lines[0]);
          lines.delete(0);
          Datawell.TableMaps.fieldbyname('I3').asinteger:=Datawell.toint(lines[0]);
          lines.delete(0);
          Datawell.TableMaps.fieldbyname('I4').asinteger:=Datawell.toint(lines[0]);
          lines.delete(0);
          Datawell.TableMaps.fieldbyname('I5').asinteger:=Datawell.toint(lines[0]);
          lines.delete(0);
          if lines[0] = 'TRUE' then bwork:=true else bwork:=false;
          Datawell.TableMaps.fieldbyname('L1').asboolean:=bwork;
          lines.delete(0);
          if lines[0] = 'TRUE' then bwork:=true else bwork:=false;
          Datawell.TableMaps.fieldbyname('L2').asboolean:=bwork;
          lines.delete(0);
          if lines[0] = 'TRUE' then bwork:=true else bwork:=false;
          Datawell.TableMaps.fieldbyname('L3').asboolean:=bwork;
          lines.delete(0);
          if lines[0] = 'TRUE' then bwork:=true else bwork:=false;
          Datawell.TableMaps.fieldbyname('L4').asboolean:=bwork;
          lines.delete(0);
          if lines[0] = 'TRUE' then bwork:=true else bwork:=false;
          Datawell.TableMaps.fieldbyname('L5').asboolean:=bwork;
          lines.delete(0);
          if lines[0] = 'TRUE' then bwork:=true else bwork:=false;
          Datawell.TableMaps.fieldbyname('ShowTags').asboolean:=bwork;
          lines.delete(0);
          if lines[0] = 'TRUE' then bwork:=true else bwork:=false;
          Datawell.TableMaps.fieldbyname('ShowShadows').asboolean:=bwork;
          lines.delete(0);
          if lines[0] = 'TRUE' then bwork:=true else bwork:=false;
          Datawell.TableMaps.fieldbyname('ShowStubs').asboolean:=bwork;
        end
        else begin
          Datawell.TableMaps.fieldbyname('LL').asboolean:=false;
          Datawell.TableMaps.fieldbyname('FColor').asinteger:=clsilver;
          Datawell.TableMaps.fieldbyname('BColor').asinteger:=clwhite;
          Datawell.TableMaps.fieldbyname('LColor').asinteger:=clblack;
          Datawell.TableMaps.fieldbyname('S1').asstring:='';
          Datawell.TableMaps.fieldbyname('S2').asstring:='';
          Datawell.TableMaps.fieldbyname('S3').asstring:='';
          Datawell.TableMaps.fieldbyname('S4').asstring:='';
          Datawell.TableMaps.fieldbyname('S5').asstring:='';
          Datawell.TableMaps.fieldbyname('I1').asinteger:=0;
          Datawell.TableMaps.fieldbyname('I2').asinteger:=0;
          Datawell.TableMaps.fieldbyname('I3').asinteger:=0;
          Datawell.TableMaps.fieldbyname('I4').asinteger:=0;
          Datawell.TableMaps.fieldbyname('I5').asinteger:=0;
          Datawell.TableMaps.fieldbyname('L1').asboolean:=false;
          Datawell.TableMaps.fieldbyname('L2').asboolean:=false;
          Datawell.TableMaps.fieldbyname('L3').asboolean:=false;
          Datawell.TableMaps.fieldbyname('L4').asboolean:=false;
          Datawell.TableMaps.fieldbyname('L5').asboolean:=false;
        end;
        if fileversion < 4700 then begin
          Datawell.TableMaps.fieldbyname('S1').asstring:='Arial';
        end;
        if fileversion < 8000 then begin
          Datawell.TableMaps.fieldbyname('I2').asinteger:=clsilver;
        end;
        lines.delete(0);
        while lines[0] <> '--------ROOMS' do begin
          memos.add(lines[0]);
          lines.delete(0);
        end;
        Datawell.TableMaps.fieldbyname('Memo').assign(memos);
        Datawell.TableMaps.post;
        //Rooms values
        lines.delete(0);
        while lines[0] <> '--------EXITS' do begin
          Datawell.TableRooms.append;
          Datawell.TableRooms.fieldbyname('room_id').asInteger:=GetNewID;
          Datawell.TableRooms.fieldbyname('Map_id').asInteger:=GetMapID(nmap);
          Datawell.TableRooms.fieldbyname('X').asinteger:=Datawell.toint(lines[0]);
          lines.delete(0);
          Datawell.TableRooms.fieldbyname('Y').asinteger:=Datawell.toint(lines[0]);
          lines.delete(0);
          Datawell.TableRooms.fieldbyname('Kind').asinteger:=Datawell.toint(lines[0]);
          lines.delete(0);
          Datawell.TableRooms.fieldbyname('Color').asinteger:=Datawell.toint(lines[0]);
          lines.delete(0);
          Datawell.TableRooms.fieldbyname('BColor').asinteger:=Datawell.toint(lines[0]);
          lines.delete(0);
          Datawell.TableRooms.fieldbyname('Label').asstring:=lines[0];
          lines.delete(0);
          Datawell.TableRooms.fieldbyname('Tag').asstring:=lines[0];
          lines.delete(0);
          Datawell.TableRooms.fieldbyname('Warn').asstring:=lines[0];
          // 4.500 - Added: Stubs, S1
          if fileversion >= 4500 then begin
            lines.delete(0);
            Datawell.TableRooms.fieldbyname('Stubs').asstring:=lines[0];
            lines.delete(0);
            Datawell.TableRooms.fieldbyname('S1').asstring:=lines[0];
          end
          else begin
            Datawell.TableRooms.fieldbyname('Stubs').asstring:='';
            Datawell.TableRooms.fieldbyname('S1').asstring:='';
          end;
          try Datawell.TableRooms.post except on EDBEngineError do Datawell.TableRooms.cancel; end;
          lines.delete(0);
        end;
        //Exits values
        lines.delete(0);
        while lines[0] <> '--------MEMOS' do begin
          Datawell.TableExits.append;
          Datawell.TableExits.fieldbyname('exit_id').asInteger:=GetNewID;
          Datawell.TableExits.fieldbyname('Map1').AsInteger:=GetMapID(nmap);
          x := Datawell.toint(lines[0]);
          Datawell.TableExits.fieldbyname('X').asinteger:=x;
          lines.delete(0);
          y := Datawell.toint(lines[0]);
          Datawell.TableExits.fieldbyname('Y').asinteger:=y;
          lines.delete(0);
          Datawell.TableExits.fieldbyname('FExit').asstring:=lines[0];
          lines.delete(0);
          if lines[0] = omap then lines[0]:=nmap;
          dMap := lines[0];
          Datawell.TableExits.fieldbyname('Map2').asInteger:=GetMapID(lines[0]);
          lines.delete(0);
          x2 := Datawell.toint(lines[0]);
          Datawell.TableExits.fieldbyname('X2').asinteger:=x2;
          lines.delete(0);
          y2 := Datawell.toint(lines[0]);
          Datawell.TableExits.fieldbyname('Y2').asinteger:=y2;
          lines.delete(0);
          Datawell.TableExits.fieldbyname('PreAction').asstring:=lines[0];
          lines.delete(0);
          Datawell.TableExits.fieldbyname('PostAction').asstring:=lines[0];
          lines.delete(0);
          Datawell.TableExits.fieldbyname('Color').asinteger:=Datawell.toint(lines[0]);
          lines.delete(0);
          Datawell.TableExits.fieldbyname('FOrder').asinteger:=Datawell.toint(lines[0]);
          Datawell.TableExits.fieldbyname('Room1').asinteger:=GetRoomID(nmap, x, y);
          Datawell.TableExits.fieldbyname('Room2').asinteger:=GetRoomID(dmap, x2, y2);
          try Datawell.TableExits.post except on EDBEngineError do Datawell.TableExits.cancel; end;
          lines.delete(0);
        end;
        //RoomMemos values
        lines.delete(0);
        while lines[0] <> '--------EOF' do begin
          x := Datawell.toint(lines[0]);
          lines.delete(0);
          y := Datawell.toint(lines[0]);
          lines.delete(0);
          memos.clear;
          while lines[0] <> '-|-|-|-|' do begin
            memos.add(lines[0]);
            lines.delete(0);
          end;
          if memos.count > 0 then
            datawell.ExecuteArbitraryQuery(format('update rooms set memo = %s where room_id = %d',
               [quotedStr(memos.Text), GetRoomID(nmap, x, y)]));
          lines.delete(0);
        end;
      finally
        lines.free;
        memos.free;
      end;
      ChangeMap(nmap);
      DrawMap(false);
      form2.close;
    finally
      form2.release;
    end;
    messagedlg('Map level ['+nmap+'] imported successfully',
      mtinformation,[mbok],0);
  end;
  if DialogOpen.files.count > 1 then messagedlg('Batch import complete',
    mtinformation,[mbok],0);
end;

const
   EXIT_FILTER_STR = '(MAP1 = %d) and (X = %d) and (Y = %d)';
   EXIT_FILTER_STR_EXT = EXIT_FILTER_STR + ' and (FEXIT = %s)';

procedure TFormMap.CreateLinkExits(map1, map2, dir1, dir2, pre1, post1, pre2, post2: string;
  x1, y1, x2, y2, col: integer; dohistory: boolean);
var
  neworder: integer;
  table: TClientDataset;
begin
  table:=TClientDataset.create(self);
  with table do begin
    CloneCursor(Datawell.TableExits, false);
    filter := format(EXIT_FILTER_STR_EXT, [GetMapID(map1), x1, y1, quotedStr(dir1)]);
    filtered := true;
    active:=true;
    if dir1 <> '' then
    begin
      if not EOF then
        edit
      else
      begin
        append;
        fieldbyname('FOrder').asinteger := Datawell.NewExitOrder(map1,x1,y1);;
      end;
      fieldbyname('FExit').asstring:=dir1;
      fieldbyname('X').asinteger:=x1;
      fieldbyname('Y').asinteger:=y1;
      fieldbyname('Map1').asInteger:=GetMapID(map1);
      fieldbyname('ROOM1').asInteger := GetRoomID(map1, x1, y1);
      fieldbyname('X2').asinteger:=x2;
      fieldbyname('Y2').asinteger:=y2;
      fieldbyname('Map2').asInteger := GetMapID(map2);
      fieldbyname('ROOM2').asInteger := GetRoomID(map2, x2, y2);
      fieldbyname('Color').asinteger:=col;
      fieldbyname('PreAction').asstring:=pre1;
      fieldbyname('PostAction').asstring:=post1;
      fieldbyname('EXIT_ID').asInteger := GetNewID;
      fieldbyname('DESTMAP').AsString := map2;
      if fieldbyname('room1').AsInteger = fieldbyname('room2').AsInteger then
        asm int 3 end;
      post;

      if dohistory then begin
        AddHistory('Exit|'+
          map1+'|'+map2+'|'+pre1+'|'+post1+'|'+
          inttostr(x1)+'|'+inttostr(y1)+'|'+
          inttostr(x2)+'|'+inttostr(y2)+'|'+dir1+'|'+inttostr(col));
      end;
      FilterStubs(map1,x1,y1);
    end;
    filter := format(EXIT_FILTER_STR_EXT, [GetMapID(map2), x2, y2, quotedStr(dir2)]);
    filtered := true;
    if dir2 <> '' then
    begin
      if not EOF then
        edit
      else
      begin
        neworder:=Datawell.NewExitOrder(map2,x2,y2);
        append;
        fieldbyname('FOrder').asinteger:=neworder;
      end;
      fieldbyname('FExit').asstring:=dir2;
      fieldbyname('X').asinteger:=x2;
      fieldbyname('Y').asinteger:=y2;
      fieldbyname('Map1').asInteger:=GetMapID(map2);
      fieldbyname('ROOM1').asInteger := GetRoomID(map2, x2, y2);
      fieldbyname('X2').asinteger:=x1;
      fieldbyname('Y2').asinteger:=y1;
      fieldbyname('Map2').asInteger:=GetMapID(map1);
      fieldbyname('ROOM2').asInteger := GetRoomID(map1, x1, y1);
      fieldbyname('Color').asinteger:=col;
      fieldbyname('PreAction').asstring:=pre2;
      fieldbyname('PostAction').asstring:=post2;
      fieldbyname('EXIT_ID').asInteger := GetNewID;
      fieldbyname('DESTMAP').AsString := map1;
      post;
      if dohistory then begin
        AddHistory('Exit|'+
          map2+'|'+map1+'|'+pre2+'|'+post2+'|'+
          inttostr(x2)+'|'+inttostr(y2)+'|'+
          inttostr(x1)+'|'+inttostr(y1)+'|'+dir2+'|'+inttostr(col));
      end;
      FilterStubs(map2,x2,y2);
    end;
  end;
  table.free;
end;

procedure TFormMap.DeleteLinkExits(map1, map2: integer; x1, y1, x2, y2: integer;
  firstonly: boolean);
const
  FILTER_STR = '(Map1 = %d) and (X = %d) and (Y = %d) and (Map2 = %d) and (X2 = %d) and (Y2 = %d)';
var
  table: TClientDataset;
begin
  table:=TClientDataset.create(self);
  //First see if there's even a link leading to that location
  with table do begin
    CloneCursor(Datawell.TableExits, false);
    filter := format(FILTER_STR, [map1, x1, y1, map2, x2, y2]);
    filtered := true;
    active:=true;
    if not EOF then begin
      AddHistory('-Exit|'+
        intToStr(map1)+'|'+intToStr(map2)+'|'+fieldbyname('PreAction').asstring+'|'+
        fieldbyname('PostAction').asstring+'|'+
        inttostr(x1)+'|'+inttostr(y1)+'|'+
        inttostr(x2)+'|'+inttostr(y2)+'|'+fieldbyname('FExit').asstring+
        '|'+inttostr(fieldbyname('Color').asinteger));
      delete;
    end;
    if not firstonly then begin
      filter := format(FILTER_STR, [map2, x2, y2, map1, x1, y1]);
      filtered := true;
      active:=true;
      if not EOF then begin
        AddHistory('-Exit|'+
          intToStr(map2)+'|'+intToStr(map1)+'|'+fieldbyname('PreAction').asstring+'|'+
          fieldbyname('PostAction').asstring+'|'+
          inttostr(x2)+'|'+inttostr(y2)+'|'+
          inttostr(x1)+'|'+inttostr(y1)+'|'+fieldbyname('FExit').asstring+
          '|'+inttostr(fieldbyname('Color').asinteger));
        delete;
      end;
    end;
  end;
  table.free;
end;

procedure TFormMap.FigureRoomExits(x1, y1, x2, y2: integer; var link1: string;
  var link2: string);
var dir1, dir2, dir3: string;
begin
  if y2 < y1 then begin
    dir1:=Datawell.Dir('s');
    dir2:=Datawell.Dir('n');
  end
  else if y2 > y1 then begin
    dir1:=Datawell.Dir('n');
    dir2:=Datawell.Dir('s');
  end
  else begin
    dir1:='';
    dir2:='';
  end;
  if x2 < x1 then begin
    dir1:=dir1+Datawell.Dir('w');
    dir2:=dir2+Datawell.Dir('e');
  end
  else if x2 > x1 then begin
    dir1:=dir1+Datawell.Dir('e');
    dir2:=dir2+Datawell.Dir('w');
  end;
  dir3:=Datawell.Reverse(dir1);
  if dir3 <> '' then dir2:=dir3;
  link1:=dir1;
  link2:=dir2;
end;

procedure TFormMap.DrawMap(drawruler: boolean);
begin
  doruler:=drawruler;
  if drawingmap then exit;
  drawingmap:=true;
  try
    ReallyDrawMap;
  finally
    drawingmap := false;
  end;
end;

procedure TFormMap.InitializeCanvas(can: TCanvas; w, h, d: integer);
var
   i, j: integer;
begin
  can.font.name:='Arial';
  can.font.size:=6;
  can.font.style:=[];
  can.pen.color:=dsCurrentMap.fieldbyname('FColor').asinteger;
  can.pen.width:=1;
  if printing then can.pen.width:=2;
  can.brush.color:=dsCurrentMap.fieldbyname('BColor').asinteger;
//Clean slate
  can.rectangle(0,0,w,h);
  if dsCurrentMap.fieldbyname('ShowGrid').asboolean then begin
//Plot horizontal gridlines
    i:=d-1;
    while i < h do begin
      can.moveto(0,i);
      can.lineto(w,i);
      inc(i,d);
    end;
//Plot vertical gridlines
    i:=d-1;
    while i < w do begin
      can.moveto(i,0);
      can.lineto(i,h);
      inc(i,d);
    end;
  end;
//Plot dots
  if (BtnEdit.down) and (not printing) then begin
    i:=(d div 2);
    while i < h do begin
      j:=(d div 2);
      while j < w do begin
        if d > 15 then can.ellipse(j-2,i-2,j+1,i+1)
        else can.pixels[j-1,i-1]:=can.pen.color;
        inc(j,(d*2));
      end;
      inc(i,(d*2));
    end;
  end;
end;

procedure TFormMap.ReallyDrawMap;
var
  i, j, d, h, w, rooms, x, y, kind, offset, rpen, p, col, scol: integer;
  x1, y1, x2, y2, xmin, ymin, xmax, ymax, n, t, u: integer;
  can: TCanvas;
  links: TStringList;
  work: string;
  stretched, skip: boolean;
  table: TClientDataset;
begin
  if not visible then exit;
  if listMaps.text = '' then exit;
  if dontdrawmap then exit;
  stretched:=false;
  col:=clblack;
  if dsCurrentMap.fieldbyname('Map').asstring = '' then exit;
  grooms:=0;
  glinks:=0;
  offset:=0;
  links:=TStringList.create;
  if printing then begin
    d:=(dsCurrentMap.fieldbyname('I1').asinteger*
      dsCurrentMap.fieldbyname('Zoom').asinteger) div 1000;
    can:=printer.canvas;
    h:=printer.pageheight;
    w:=printer.pagewidth;
    inc(offset,2);
    if d >= 10 then inc(offset,2);
    if d >= 20 then inc(offset,2);
    if d >= 30 then inc(offset,2);
    if d >= 40 then inc(offset,2);
    xmin:=-9999; ymin:=-9999;
    xmax:=9999; ymax:=9999;
  end
  else begin
    d:=dsCurrentMap.fieldbyname('Zoom').asinteger div 10;
    can:=MapCanvas.canvas;
    h:=MapCanvas.height;
    w:=MapCanvas.width;
    if d >= 10 then inc(offset);
    if d >= 20 then inc(offset);
    if d >= 30 then inc(offset);
    if d >= 40 then inc(offset);
    //Figure map minimums
    xmin:=xoffset div -2;
    ymax:=yoffset div 2;
    xmax:=xmin+(MapCanvas.width div (d*2))+1;
    ymin:=ymax-(MapCanvas.height div (d*2))-1;
  end;
  if d >= 20 then rpen:=2
  else rpen:=1;
  InitializeCanvas(can, w, h, d);
//Plot basement
  if (dsCurrentMap.fieldbyname('L3').asboolean) and (dsCurrentMap.fieldbyname('S2').asstring <> '') then
  begin
    with Datawell.TableExitsAlt do begin
      //Plot basement links
      filter := format('Map1 = %d', [GetMapID(dsCurrentMap.fieldbyname('S2').asstring)]);
      filtered := true;
      active:=true;
      links.clear;
      first;
      while not EOF do begin
        if ((fieldbyname('X').asinteger < xmin) or (fieldbyname('X').asinteger > xmax) or
           (fieldbyname('Y').asinteger < ymin) or (fieldbyname('Y').asinteger > ymax)) and
           ((fieldbyname('X2').asinteger < xmin) or (fieldbyname('X2').asinteger > xmax) or
           (fieldbyname('Y2').asinteger < ymin) or (fieldbyname('Y2').asinteger > ymax)) then begin
          next;
          continue;
        end;
        if fieldbyname('Map2').asInteger = GetMapID(dsCurrentMap.fieldbyname('S2').asstring) then begin
          work:=inttostr(fieldbyname('X2').asinteger)+'|'+
            inttostr(fieldbyname('Y2').asinteger)+'|'+
            inttostr(fieldbyname('X').asinteger)+'|'+
            inttostr(fieldbyname('Y').asinteger)+'|'+
            inttostr(fieldbyname('Color').asinteger);
          x:=links.indexof(work);
          if x < 0 then begin
            links.add(inttostr(fieldbyname('X').asinteger)+'|'+
              inttostr(fieldbyname('Y').asinteger)+'|'+
              inttostr(fieldbyname('X2').asinteger)+'|'+
              inttostr(fieldbyname('Y2').asinteger)+'|'+
              inttostr(fieldbyname('Color').asinteger));
          end
          else links[x]:='*'+links[x];
        end;
        next;
      end;
      can.pen.width:=rpen;
      for x:=0 to links.count-1 do begin
        kind:=1;
        if copy(links[x],1,1) = '*' then begin
          kind:=0;
          links[x]:=copy(links[x],2,99999);
        end;
        work:=links[x];
        y:=pos('|',work);
        x1:=strtointDef(copy(work,1,y-1), 0);
        system.delete(work,1,y);
        y:=pos('|',work);
        y1:=strtointDef(copy(work,1,y-1), 0);
        system.delete(work,1,y);
        y:=pos('|',work);
        x2:=strtointDef(copy(work,1,y-1), 0);
        system.delete(work,1,y);
        y:=pos('|',work);
        y2:=strtointDef(copy(work,1,y-1), 0);
        system.delete(work,1,y);
        col:=strtointDef(work, 0);
        can.pen.width:=rpen;
        PlotLink(1,x1,y1,x2,y2,kind,d,dsCurrentMap.fieldbyname('I2').asinteger,
          '',false,can);
      end;
    end;
      //Plot basement rooms
    with Datawell.TableRoomsAlt do
    begin
      filter := format('Map_id = %d', [GetMapID(dsCurrentMap.fieldbyname('S2').asstring)]);
      filtered := true;
      active:=true;
      first;
      while not EOF do begin
        if (fieldbyname('X').asinteger < xmin) or
           (fieldbyname('X').asinteger > xmax) or
           (fieldbyname('Y').asinteger < ymin) or
           (fieldbyname('Y').asinteger > ymax) then begin
          next;
          continue;
        end;
        PlotRoom(1,fieldbyname('X').asinteger,
          fieldbyname('Y').asinteger,
          fieldbyname('Kind').asinteger,d,rpen,
          dsCurrentMap.fieldbyname('I2').asinteger,
          dsCurrentMap.fieldbyname('BColor').asinteger,
          fieldbyname('Label').asstring,
          fieldbyname('Tag').asstring,
          fieldbyname('Stubs').asstring,can);
        next;
      end;
    end;
  end;
//Plot (0,0) crosshairs
  can.pen.width:=1;
  can.pen.color:=clgreen;
  //top line
  can.moveto((xoffset-1)*d+(d div 2)-1,yoffset*d-1);
  can.lineto((xoffset+2)*d-(d div 2)-1,yoffset*d-1);
  //bottom line
  can.moveto((xoffset-1)*d+(d div 2)-1,(yoffset+1)*d-1);
  can.lineto((xoffset+2)*d-(d div 2)-1,(yoffset+1)*d-1);
  //left line
  can.moveto((xoffset)*d-1,(yoffset-1)*d+(d div 2)-1);
  can.lineto((xoffset)*d-1,(yoffset+2)*d-(d div 2)-1);
  //right line
  can.moveto((xoffset+1)*d-1,(yoffset-1)*d+(d div 2)-1);
  can.lineto((xoffset+1)*d-1,(yoffset+2)*d-(d div 2)-1);
//Plot stubs
  can.pen.width:=rpen;
  with Datawell.TableRooms do begin
    first;
    while not EOF do begin
      if (Datawell.TableRooms.fieldbyname('X').asinteger < xmin) or
         (Datawell.TableRooms.fieldbyname('X').asinteger > xmax) or
         (Datawell.TableRooms.fieldbyname('Y').asinteger < ymin) or
         (Datawell.TableRooms.fieldbyname('Y').asinteger > ymax) then begin
        next;
        continue;
      end;
      PlotStubs(Datawell.TableRooms.fieldbyname('X').asinteger,
        Datawell.TableRooms.fieldbyname('Y').asinteger,
        d,offset,Datawell.TableRooms.fieldbyname('Stubs').asstring,
        Datawell.TableRooms.fieldbyname('Label').asstring,can);
      next;
    end;
  end;
//Plot links
  with Datawell.TableExits do begin
    links.clear;
    first;
    while not EOF do begin
      inc(glinks);
      if ((fieldbyname('X').asinteger < xmin) or (fieldbyname('X').asinteger > xmax) or
         (fieldbyname('Y').asinteger < ymin) or (fieldbyname('Y').asinteger > ymax)) and
         ((fieldbyname('X2').asinteger < xmin) or (fieldbyname('X2').asinteger > xmax) or
         (fieldbyname('Y2').asinteger < ymin) or (fieldbyname('Y2').asinteger > ymax)) then begin
        next;
        continue;
      end;
      if fieldbyname('Map2').asInteger = dsCurrentMap.fieldbyname('Map_ID').asInteger then begin
        work:=inttostr(fieldbyname('X2').asinteger)+'|'+
          inttostr(fieldbyname('Y2').asinteger)+'|'+
          inttostr(fieldbyname('X').asinteger)+'|'+
          inttostr(fieldbyname('Y').asinteger)+'|'+
          inttostr(fieldbyname('Color').asinteger);
        x:=links.indexof(work);
        if x < 0 then begin
          links.add(inttostr(fieldbyname('X').asinteger)+'|'+
            inttostr(fieldbyname('Y').asinteger)+'|'+
            inttostr(fieldbyname('X2').asinteger)+'|'+
            inttostr(fieldbyname('Y2').asinteger)+'|'+
            inttostr(fieldbyname('Color').asinteger));
        end
        else links[x]:='*'+links[x];
      end;
      next;
    end;
    can.pen.width:=rpen;
    for x:=0 to links.count-1 do begin
      kind:=1;
      if copy(links[x],1,1) = '*' then begin
        kind:=0;
        links[x]:=copy(links[x],2,99999);
      end;
      work:=links[x];
      y:=pos('|',work);
      x1:=strtointDef(copy(work,1,y-1), 0);
      system.delete(work,1,y);
      y:=pos('|',work);
      y1:=strtointDef(copy(work,1,y-1), 0);
      system.delete(work,1,y);
      y:=pos('|',work);
      x2:=strtointDef(copy(work,1,y-1), 0);
      system.delete(work,1,y);
      y:=pos('|',work);
      y2:=strtointDef(copy(work,1,y-1), 0);
      system.delete(work,1,y);
      col:=strtointDef(work, 0);
      //Plot stretched line (delete a link = red, prioritize a link = green)
      scol:=clred;
      if (stretching) and
        (((x1 = SX) and (y1 = SY) and (x2 = GX) and (y2 = GY)) or
        ((x2 = SX) and (y2 = SY) and (x1 = GX) and (y1 = GY))) then begin
        if d <= 10 then can.pen.width:=2 else can.pen.width:=4;
        PlotLink(0,x1,y1,x2,y2,kind,d,scol,'',true,can);
        stretched:=true;
      end
      else begin
        can.pen.width:=rpen;
        PlotLink(0,x1,y1,x2,y2,kind,d,col,'',false,can);
      end;
    end;
  end;
//Plot stretched line (add a link = blue, move a room = teal)
  if (stretching) and (not stretched) then begin
    if d <= 10 then can.pen.width:=2 else can.pen.width:=4;
    can.pen.color:=clblue;
    if CleanRoom(dsCurrentMap.fieldbyname('Map').asstring,GX,GY) < 1 then begin
      if Datawell.selx.count > 0 then begin
        p:=Datawell.selx.indexof(inttostr(SX)+'|'+inttostr(SY));
        if p >= 0 then can.pen.color:=clteal;
      end;
    end;
    offset:=d div 2;
    can.moveto(SX*d*2+xoffset*d+offset,-SY*d*2+yoffset*d+offset);
    can.lineto(GX*d*2+xoffset*d+offset,-GY*d*2+yoffset*d+offset);
  end;
//Plot rooms
  with Datawell.TableRooms do begin
    first;
    while not EOF do begin
      inc(grooms);
      if (fieldbyname('X').asinteger < xmin) or
         (fieldbyname('X').asinteger > xmax) or
         (fieldbyname('Y').asinteger < ymin) or
         (fieldbyname('Y').asinteger > ymax) then begin
        next;
        continue;
      end;
      PlotRoom(0,fieldbyname('X').asinteger,
        fieldbyname('Y').asinteger,
        fieldbyname('Kind').asinteger,d,rpen,
        fieldbyname('Color').asinteger,
        fieldbyname('BColor').asinteger,
        fieldbyname('Label').asstring,
        fieldbyname('Tag').asstring,
        fieldbyname('Stubs').asstring,can);
      next;
    end;
  end;
//Plot attic
  if (dsCurrentMap.fieldbyname('L4').asboolean) and (dsCurrentMap.fieldbyname('S3').asstring <> '') then
  begin
    with Datawell.TableExitsAlt do begin
      //Plot attic links
      filter := format('Map1 = %d', [GetMapID(dsCurrentMap.fieldbyname('S3').asstring)]);
      filtered := true;
      active := true;
      links.clear;
      first;
      while not EOF do
      begin
        if ((fieldbyname('X').asinteger < xmin) or (fieldbyname('X').asinteger > xmax) or
           (fieldbyname('Y').asinteger < ymin) or (fieldbyname('Y').asinteger > ymax)) and
           ((fieldbyname('X2').asinteger < xmin) or (fieldbyname('X2').asinteger > xmax) or
           (fieldbyname('Y2').asinteger < ymin) or (fieldbyname('Y2').asinteger > ymax)) then begin
          next;
          continue;
        end;
        if fieldbyname('Map2').asInteger = GetMapID(dsCurrentMap.fieldbyname('S3').asstring) then begin
          work:=inttostr(fieldbyname('X2').asinteger)+'|'+
            inttostr(fieldbyname('Y2').asinteger)+'|'+
            inttostr(fieldbyname('X').asinteger)+'|'+
            inttostr(fieldbyname('Y').asinteger)+'|'+
            inttostr(fieldbyname('Color').asinteger);
          x:=links.indexof(work);
          if x < 0 then begin
            links.add(inttostr(fieldbyname('X').asinteger)+'|'+
              inttostr(fieldbyname('Y').asinteger)+'|'+
              inttostr(fieldbyname('X2').asinteger)+'|'+
              inttostr(fieldbyname('Y2').asinteger)+'|'+
              inttostr(fieldbyname('Color').asinteger));
          end
          else links[x]:='*'+links[x];
        end;
        next;
      end;
      can.pen.width:=rpen;
      for x:=0 to links.count-1 do begin
        kind:=1;
        if copy(links[x],1,1) = '*' then begin
          kind:=0;
          links[x]:=copy(links[x],2,99999);
        end;
        work:=links[x];
        y:=pos('|',work);
        x1:=StrToIntDef(copy(work,1,y-1), 0);
        system.delete(work,1,y);
        y:=pos('|',work);
        y1:=StrToIntDef(copy(work,1,y-1), 0);
        system.delete(work,1,y);
        y:=pos('|',work);
        x2:=StrToIntDef(copy(work,1,y-1), 0);
        system.delete(work,1,y);
        y:=pos('|',work);
        y2:=StrToIntDef(copy(work,1,y-1), 0);
        system.delete(work,1,y);
        can.pen.width:=rpen;
        PlotLink(-1,x1,y1,x2,y2,kind,d,dsCurrentMap.fieldbyname('I3').asinteger,
          '',false,can);
      end;
    end;
    //Plot attic rooms
    with Datawell.TableRoomsAlt do
    begin
      filter := format('Map_id = %d', [GetMapID(dsCurrentMap.fieldbyname('S3').asstring)]);
      filtered := true;
      active := true;
      first;
      while not EOF do begin
        if (fieldbyname('X').asinteger < xmin) or
           (fieldbyname('X').asinteger > xmax) or
           (fieldbyname('Y').asinteger < ymin) or
           (fieldbyname('Y').asinteger > ymax) then begin
          next;
          continue;
        end;
        PlotRoom(-1,fieldbyname('X').asinteger,
          fieldbyname('Y').asinteger,
          fieldbyname('Kind').asinteger,d,rpen,
          dsCurrentMap.fieldbyname('I3').asinteger,
          dsCurrentMap.fieldbyname('BColor').asinteger,
          fieldbyname('Label').asstring,
          fieldbyname('Tag').asstring,
          fieldbyname('Stubs').asstring,can);
        next;
      end;
    end;
  end;
//Plot current location (if on this map)
  if (visible) and (CMAP = dsCurrentMap.fieldbyname('Map').asstring) then begin
    x:=CX*2;
    y:=CY*-2;
    can.pen.color:=clgreen;
    can.pen.width:=2;
    if d >= 10 then can.pen.width:=3;
    if d >= 25 then can.pen.width:=5;
    if d >= 40 then can.pen.width:=6;
    can.pen.color:=clblack;
    //back slash background
    can.moveto(round((x-0.33+xoffset)*d)+1,round((y+0.05+yoffset)*d)+1);
    can.lineto(round((x+1.33+xoffset)*d)-2,round((y+0.95+yoffset)*d)-2);
    //forward slash background
    can.moveto(round((x+0.95+xoffset)*d)-2,round((y-0.33+yoffset)*d)+1);
    can.lineto(round((x+0.05+xoffset)*d)+1,round((y+1.33+yoffset)*d)-2);
    can.pen.color:=clgreen;
    //back slash
    can.moveto(round((x-0.33+xoffset)*d)+0,round((y+0.05+yoffset)*d)+0);
    can.lineto(round((x+1.33+xoffset)*d)-3,round((y+0.95+yoffset)*d)-3);
    //forward slash
    can.moveto(round((x+0.95+xoffset)*d)-3,round((y-0.33+yoffset)*d)+0);
    can.lineto(round((x+0.05+xoffset)*d)+0,round((y+1.33+yoffset)*d)-3);
    can.pen.color:=cllime;
    can.pen.width:=can.pen.width-3;
    if can.pen.width < 1 then can.pen.width:=1;
    //highlight back slash
    can.moveto(round((x-0.33+xoffset)*d)+0,round((y+0.05+yoffset)*d)+0);
    can.lineto(round((x+1.33+xoffset)*d)-3,round((y+0.95+yoffset)*d)-3);
    //highlight forward slash
    can.moveto(round((x+0.95+xoffset)*d)-3,round((y-0.33+yoffset)*d)+0);
    can.lineto(round((x+0.05+xoffset)*d)+0,round((y+1.33+yoffset)*d)-3);
  end;
//Plot map name if printing
  if printing and (dsCurrentMap.fieldbyname('L1').asboolean) then begin
    can.brush.color:=dsCurrentMap.fieldbyname('BColor').asinteger;
    can.brush.style:=bssolid;
    can.font.color:=dsCurrentMap.fieldbyname('I4').asinteger;
    can.font.name:='Arial';
    can.font.size:=12;
    can.font.style:=[fsbold];
    can.textout(17,15,dsCurrentMap.fieldbyname('Map').asstring);
  end;
  links.free;
  UpdateCaption;
  Datawell.idle:=0;
  if (printing) or (not doruler) then exit;
  //Draw ruler
  doruler:=false;
  d:=dsCurrentMap.fieldbyname('Zoom').asinteger div 10;
  p:=-1;
  n:=-xoffset;
  skip:=true;
  if (xoffset mod 2) = 0 then skip:=false;
  with Ruler1.canvas do begin
    brush.color:=clsilver;
    pen.color:=clblack;
    pen.width:=1;
    font.color:=clblack;
    font.name:='Arial';
    font.size:=7;
    if d < 10 then font.size:=6;
    rectangle(-1,-1,Ruler1.width,Ruler1.height);
    while p < Ruler1.width do begin
      x1:=p;
      x2:=x1;
      y2:=25;
      if not skip then begin
        u:=n div 2;
        if u = 0 then font.color:=clgreen
        else if u < 0 then font.color:=clred
        else font.color:=clblack;
        if d = 5 then textout(p+(d div 2)-2,6,inttostr(abs(u)))
        else begin
          t:=p;
          if d >= 10 then inc(t);
          if d >= 15 then inc(t);
          textout(t+(d div 2)-4,1,inttostr(abs(u)));
        end;
        y1:=6;
      end
      else y1:=15;
      inc(n);
      skip:=not skip;
      moveto(x1,y1); lineto(x2,y2);
      inc(p,d);
    end;
  end;
  p:=-1;
  n:=-yoffset;
  skip:=true;
  if (yoffset mod 2) = 0 then skip:=false;
  with Ruler2.canvas do begin
    brush.color:=clsilver;
    pen.color:=clblack;
    pen.width:=1;
    font.color:=clblack;
    font.name:='Arial';
    font.size:=7;
    if d < 10 then font.size:=6;
    rectangle(-1,-1,Ruler2.width,Ruler2.height);
    while p < Ruler2.height do begin
      y1:=p;
      x2:=25;
      y2:=p;
      if not skip then begin
        u:=n div 2;
        if u = 0 then font.color:=clgreen
        else if u > 0 then font.color:=clred
        else font.color:=clblack;
        if d = 5 then begin
          textout(2,p+(d div 2)-1,inttostr(abs(u)));
        end
        else begin
          t:=p;
          if d >= 10 then inc(t);
          if d >= 15 then inc(t);
          textout(2,t+(d div 2)-7,inttostr(abs(u)));
        end;
        x1:=6;
      end
      else x1:=15;
      inc(n);
      skip:=not skip;
      moveto(x1,y1); lineto(x2,y2);
      inc(p,d);
    end;
  end;
end;

procedure TFormMap.PlotStubs(x, y, d, offset: integer; stubs, lab: string; can: TCanvas);
var x2, y2: integer;
begin
  x:=x*2; y:=y*-2;
  with can do begin
    if dsCurrentMap.fieldbyname('ShowStubs').asboolean and (stubs <> '') then begin
      pen.color:=dsCurrentMap.fieldbyname('SColor').asinteger;
      brush.color:=dsCurrentMap.fieldbyname('SColor').asinteger;
      x2:=trunc((x+0.5+xoffset)*d);
      y2:=trunc((y+0.5+yoffset)*d);
      if pos('8',stubs) > 0 then begin
        moveto(x2,(y+yoffset)*d);
        lineto(x2,trunc((y-0.33+yoffset)*d));
      end;
      if pos('2',stubs) > 0 then begin
        moveto(x2,(y+1+yoffset)*d-2);
        lineto(x2,trunc((y+1.33+yoffset)*d)-2);
      end;
      if pos('6',stubs) > 0 then begin
        moveto((x+1+xoffset)*d-2,y2);
        lineto(trunc((x+1.33+xoffset)*d)-2,y2);
      end;
      if pos('4',stubs) > 0 then begin
        moveto((x+xoffset)*d,y2);
        lineto(trunc((x-0.33+xoffset)*d),y2);
      end;
      if pos('9',stubs) > 0 then begin
        moveto((x+1+xoffset)*d-2,(y+yoffset)*d);
        lineto(round((x+1.25+xoffset)*d)-2,round((y-0.25+yoffset)*d));
      end;
      if pos('3',stubs) > 0 then begin
        moveto((x+1+xoffset)*d-2,(y+1+yoffset)*d-2);
        lineto(round((x+1.25+xoffset)*d)-2,round((y+1.25+yoffset)*d)-2);
      end;
      if pos('1',stubs) > 0 then begin
        moveto((x+xoffset)*d,(y+1+yoffset)*d-2);
        lineto(round((x-0.25+xoffset)*d),round((y+1.25+yoffset)*d)-2);
      end;
      if pos('7',stubs) > 0 then begin
        moveto((x+xoffset)*d,(y+yoffset)*d);
        lineto(round((x-0.25+xoffset)*d),round((y-0.25+yoffset)*d));
      end;
      if pos('5',stubs) > 0 then begin
        ellipse(round((x+1.15+xoffset)*d)-1,round((y+0.05+yoffset)*d)+1,
          round((x+1.4+xoffset)*d)-1,round((y+0.3+yoffset)*d)+1);
      end;
    end;
  end;
end;

procedure TFormMap.PlotRoom(level, x, y, kind, d, penw: integer; col, bcol: TColor;
  lab, stag, stubs: string; can: TCanvas);
var d2, x1, x2, x3, y1, y2, y3, xo, yo, bo, P: integer;
begin
  xo:=x; yo:=y;
  x:=x*2; y:=y*-2;
  with can do begin
    if printing then pen.width:=dsCurrentMap.fieldbyname('I1').asinteger div 72;
    if (level = 0) and (highlightstubs) and (stubs <> '') then begin
      brush.color:=col;
      pen.color:=bcol;
    end
    else begin
      brush.color:=bcol;
      pen.color:=col;
    end;
    case level of
     -1: begin
           bo:=-((d div 4)+1);
           penw:=1;
           brush.style:=bsclear;
           pen.style:=psdot;
         end;
      0: bo:=0;
      1: bo:=(d div 4)+1;
      else raise Exception.CreateFmt('TFormMap.PlotRoom: Invalid Level value %d', [level]);
    end;
    pen.width:=penw;
    if (level = 0) and (Datawell.selx.count > 0) then begin
      p:=Datawell.selx.indexof(inttostr(xo)+'|'+inttostr(yo));
      if p >= 0 then begin
        if p = 0 then pen.color:=clteal else pen.color:=clgreen;
        pen.width:=pen.width*3;
      end;
    end;
    x1:=(x+xoffset)*d;
    y1:=(y+yoffset)*d;
    x2:=(x+1+xoffset)*d-1;
    y2:=(y+1+yoffset)*d-1;
    if (level > -1) and (d >= 20) then begin
      inc(x1,1);
      inc(y1,1);
    end;
    //Draw basic room
    rectangle(x1+bo,y1+bo,x2+bo,y2+bo);
    pen.color:=col;
    pen.width:=penw;
    if kind = 1 then begin //uparrow
      if level > -1 then brush.color:=col;
      x1:=(x+xoffset)*d+d*10 div 20;
      y1:=(y+yoffset)*d+d*4 div 20;
      x2:=(x+xoffset)*d+d*14 div 20;
      y2:=(y+yoffset)*d+d*8 div 20;
      x3:=(x+xoffset)*d+d*6 div 20;
      y3:=y2;
      polygon([point(x1+bo,y1+bo),point(x2+bo,y2+bo),point(x3+bo,y3+bo)]);
      x1:=(x+xoffset)*d+d*9 div 20;
      y1:=(y+yoffset)*d+d*8 div 20;
      y2:=(y+yoffset)*d+d*16 div 20;
      x3:=(x+xoffset)*d+d*23 div 40;
      y3:=y2;
      rectangle(x1+bo,y1+bo,x3+bo+1,y3+bo+1);
    end
    else if kind = 2 then begin //downarrow
      if level > -1 then brush.color:=col;
      x1:=(x+xoffset)*d+d*5 div 10;
      y1:=(y+yoffset)*d+d*8 div 10;
      x2:=(x+xoffset)*d+d*7 div 10;
      y2:=(y+yoffset)*d+d*6 div 10;
      x3:=(x+xoffset)*d+d*3 div 10;
      y3:=y2;
      polygon([point(x1+bo,y1+bo),point(x2+bo,y2+bo),point(x3+bo,y3+bo)]);
      x1:=(x+xoffset)*d+d*9 div 20;
      y1:=(y+yoffset)*d+d*4 div 20;
      y2:=(y+yoffset)*d+d*13 div 20;
      x3:=(x+xoffset)*d+d*23 div 40;
      y3:=y2;
      rectangle(x1+bo,y1+bo,x3+bo+1,y3+bo+1);
    end
    else if kind = 3 then begin //up/down arrows
      if level > -1 then brush.color:=col;
      x1:=(x+xoffset)*d+d*5 div 10;
      y1:=(y+yoffset)*d+d*8 div 10;
      x2:=(x+xoffset)*d+d*7 div 10;
      y2:=(y+yoffset)*d+d*6 div 10;
      x3:=(x+xoffset)*d+d*3 div 10;
      y3:=y2;
      polygon([point(x1,y1),point(x2,y2),point(x3,y3)]);
      x1:=(x+xoffset)*d+d*5 div 10;
      y1:=(y+yoffset)*d+d*2 div 10;
      x2:=(x+xoffset)*d+d*7 div 10;
      y2:=(y+yoffset)*d+d*4 div 10;
      x3:=(x+xoffset)*d+d*3 div 10;
      y3:=y2;
      polygon([point(x1,y1),point(x2,y2),point(x3,y3)]);
      x1:=(x+xoffset)*d+d*9 div 20;
      y1:=(y+yoffset)*d+d*8 div 20;
      y2:=(y+yoffset)*d+d*13 div 20;
      x3:=(x+xoffset)*d+d*23 div 40;
      y3:=y2;
      rectangle(x1+bo,y1+bo,x3+bo+1,y3+bo+1);
    end
    else if kind = 4 then begin //asterisk
      if level > -1 then brush.color:=col;
      //nw-se line
      pen.width:=1;
      if (level > -1) and (d >= 40) then pen.width:=2;
      x1:=(x+xoffset)*d+d*11 div 40;
      y1:=(y+yoffset)*d+d*11 div 40;
      x2:=(x+xoffset)*d+d*27 div 40;
      y2:=(y+yoffset)*d+d*27 div 40;
      moveto(x1+bo,y1+bo); lineto(x2+bo+1,y2+bo+1);
      //ne-sw line
      x1:=(x+xoffset)*d+d*27 div 40;
      y1:=(y+yoffset)*d+d*11 div 40;
      x2:=(x+xoffset)*d+d*11 div 40;
      y2:=(y+yoffset)*d+d*27 div 40;
      moveto(x1+bo,y1+bo); lineto(x2+bo-1,y2+bo+1);
      //w-e line
      pen.width:=1;
      if (level > -1) and (d >= 40) then pen.width:=3;
      x1:=(x+xoffset)*d+d*31 div 40;
      y1:=(y+yoffset)*d+d*19 div 40;
      x2:=(x+xoffset)*d+d*7 div 40;
      y2:=(y+yoffset)*d+d*19 div 40;
      moveto(x1+bo,y1+bo); lineto(x2+bo,y2+bo);
      //n-s line
      x1:=(x+xoffset)*d+d*19 div 40;
      y1:=(y+yoffset)*d+d*7 div 40;
      x2:=(x+xoffset)*d+d*19 div 40;
      y2:=(y+yoffset)*d+d*31 div 40;
      moveto(x1+bo,y1+bo); lineto(x2+bo,y2+bo);
    end
    else if kind = 5 then begin //circle
      if level > -1 then brush.color:=col;
      pen.width:=1;
      x1:=(x+xoffset)*d+d*8 div 40;
      y1:=(y+yoffset)*d+d*8 div 40;
      x2:=(x+xoffset)*d+d*31 div 40;
      y2:=(y+yoffset)*d+d*31 div 40;
      ellipse(x1+bo,y1+bo,x2+bo,y2+bo);
    end;
    if (level = 0) and (dsCurrentMap.fieldbyname('ShowLabels').asboolean) then begin
      if lab <> '' then begin
        font.size:=14;
        d2:=dsCurrentMap.fieldbyname('Zoom').asinteger div 10;
        case d2 of
          5: font.size:=4;
          10: font.size:=5;
          15: font.size:=8;
          20: font.size:=10;
          25: font.size:=11;
          30: font.size:=12;
          35: font.size:=13;
          40: font.size:=14;
          45: font.size:=18;
          50: font.size:=20;
        end;
        if printing then font.size:=(font.size*2) div 3;
        font.style:=[];
        font.name:=dsCurrentMap.fieldbyname('S1').asstring;
        font.color:=dsCurrentMap.fieldbyname('LColor').asinteger;
        brush.color:=dsCurrentMap.fieldbyname('BColor').asinteger;
        if dsCurrentMap.fieldbyname('LL').asboolean then
          brush.style:=bssolid else brush.style:=bsclear;
        if d <= 10 then
          textout((x+xoffset)*d+(d div 10),trunc((y+0.9+yoffset)*d)+(d*2 div 20),lab)
        else if d = 15 then
          textout((x+xoffset)*d+(d div 10),trunc((y+0.95+yoffset)*d)+(d*2 div 20),lab)
        else textout((x+xoffset)*d+(d div 10),(y+1+yoffset)*d+(d*2 div 20),lab);
        brush.style:=bssolid;
      end;
    end;
    if (dsCurrentMap.fieldbyname('ShowTags').asboolean) and (stag <> '') and (kind = 0) then begin
      font.size:=20;
      d2:=dsCurrentMap.fieldbyname('Zoom').asinteger div 10;
      case d2 of
        5: font.size:=3;
        10: font.size:=4;
        15: font.size:=6;
        20: font.size:=8;
        25: font.size:=12;
        30: font.size:=14;
        35: font.size:=16;
        40: font.size:=18;
        45: font.size:=22;
        50: font.size:=24;
      end;
      if printing then font.size:=(font.size*2) div 3;
      font.name:='Arial';
      font.style:=[fsbold];
      font.color:=col;
      brush.color:=bcol;
      brush.style:=bsclear;
      textout((x+xoffset)*d+(d div 4)+bo,(y+yoffset)*d+(d*2 div 15)+bo,stag);
      brush.style:=bssolid;
    end;
    brush.style:=bssolid;
    pen.style:=pssolid;
  end;
end;

procedure TFormMap.PlotLink(level, x1, y1, x2, y2, kind, d: integer; col: TColor;
  lab: string; stretched: boolean; can: TCanvas);
var
  xx1, yy1, xx2, yy2, xx3, yy3, offset: integer;
  xmid, ymid, rise, run, xu, yu, xd, yd, bo: integer;
begin
  x1:=x1*2; y1:=y1*-2; x2:=x2*2; y2:=y2*-2;
  offset:=d div 2;
  xd:=offset+(xoffset*d);
  yd:=offset+(yoffset*d);
  with can do begin
    case level of
     -1: begin
           bo:=-((d div 4)+1);
           brush.style:=bsclear;
           pen.width:=1;
           pen.style:=psdot;
         end;
      0: bo:=0;
      1: begin
           bo:=d div 4;
           if d > 10 then inc(bo);
         end;
      else raise Exception.CreateFmt('TFormMap.PlotLink: Invalid Level value %d', [level]);
    end;
    pen.color:=col;
    x1:=(x1*d)+xd;
    y1:=(y1*d)+yd;
    x2:=(x2*d)+xd;
    y2:=(y2*d)+yd;
    if level = -1 then begin
      if x2 > x1 then begin
        x1:=x1+offset;
        x2:=x2-offset;
      end
      else if x1 > x2 then begin
        x1:=x1-offset;
        x2:=x2+offset;
      end;
      if y2 > y1 then begin
        y1:=y1+offset;
        y2:=y2-offset;
      end
      else if y1 > y2 then begin
        y1:=y1-offset;
        y2:=y2+offset;
      end;
    end;
    //Draw line
    moveto(x1+bo,y1+bo); lineto(x2+bo,y2+bo);
    if kind > 0 then begin
      offset:=d div 5;
      xmid:=x1+((x2-x1) div 2);
      ymid:=y1+((y2-y1) div 2);
      run:=x2-x1;
      rise:=y2-y1;
      if (kind = 1) or (kind = 4) then begin //arrow link
        if level > -1 then begin
          if kind = 1 then brush.color:=col
          else brush.color:=clwhite;
        end;
        pen.width:=1;
        if printing then pen.width:=dsCurrentMap.fieldbyname('I1').asinteger div 72;
        if run = 0 then begin //vertical
          xx1:=xmid-offset;
          xx2:=xmid+offset;
          xx3:=xmid;
          yy1:=ymid;
          yy2:=ymid;
          if rise > 0 then yy3:=ymid+offset
          else yy3:=ymid-offset;
        end
        else if rise = 0 then begin //horizontal
          xx1:=xmid;
          xx2:=xmid;
          if run > 0 then xx3:=xmid+offset
          else xx3:=xmid-offset;
          yy1:=ymid-offset;
          yy2:=ymid+offset;
          yy3:=ymid;
        end
        else begin
          run:=x2-x1;
          rise:=y2-y1;
          xu:=trunc(d/(3*sqrt(1+sqr(run/rise))));
          yu:=trunc(xu*run/rise);
          if (run > 0) and (rise > 0) then begin
            xx1:=xmid+xu;
            xx2:=xmid-xu;
            xx3:=xmid+yu;
            yy1:=ymid-yu;
            yy2:=ymid+yu;
            yy3:=ymid+xu;
          end
          else if (run > 0) and (rise < 0) then begin
            xx1:=xmid+xu;
            xx2:=xmid-xu;
            xx3:=xmid-yu;
            yy1:=ymid-yu;
            yy2:=ymid+yu;
            yy3:=ymid-xu;
          end
          else if (run < 0) and (rise > 0) then begin
            xx1:=xmid+xu;
            xx2:=xmid-xu;
            xx3:=xmid+yu;
            yy1:=ymid-yu;
            yy2:=ymid+yu;
            yy3:=ymid+xu;
          end
          else begin
            xx1:=xmid+xu;
            xx2:=xmid-xu;
            xx3:=xmid-yu;
            yy1:=ymid-yu;
            yy2:=ymid+yu;
            yy3:=ymid-xu;
          end;
        end;
        polygon([point(xx1+bo,yy1+bo),point(xx2+bo,yy2+bo),point(xx3+bo,yy3+bo)]);
      end;
    end;
  end;
end;

const
  ROOM_FILTER_STR = '(MAP_ID = %d) and (X = %d) and (Y = %d)';

function TFormMap.RoomAt(map: string; x, y: integer): boolean;
var table: TClientDataset;
begin
  table:=TClientDataset.create(self);
  try
    table.CloneCursor(Datawell.TableRooms, false);
    result := table.Locate('MAP_ID;X;Y', VarArrayOf([GetMapID(map), x, y]), []);
  finally
    table.free;
  end;
end;

function TFormMap.WarningRoom(map: string; x, y: integer): string;
var table: TClientDataset;
begin
  table:=TClientDataset.create(self);
  with table do begin
    CloneCursor(Datawell.TableRooms, false);
    filter := format(ROOM_FILTER_STR, [GetMapID(map), x, y]);
    filtered := true;
    active:=true;
    result:=fieldbyname('Warn').asstring;
  end;
  table.free;
end;

procedure TFormMap.AddHistory(str: string);
var
  p, workingid, id: integer;
  work: string;
begin
  if undoing then exit;
  Undos.insert(0,inttostr(undoid)+'|'+str);
  MenuUndo.enabled:=true;
  while Undos.count > undomax do begin
    work:=Undos[Undos.count-1];
    p:=pos('|',work);
    workingid:=Datawell.toint(copy(work,1,p-1));
    id:=workingid;
    while id = workingid do begin
      Undos.delete(Undos.count-1);
      work:=Undos[Undos.count-1];
      p:=pos('|',work);
      id:=Datawell.toint(copy(work,1,p-1));
    end;
  end;
end;

procedure TFormMap.UpdateFilters(const map: string);
var
   mapid: integer;
begin
  mapid := GetMapID(map);

  if datawell.TableRooms.Active then
    datawell.TableRooms.ApplyUpdates(1);
  datawell.TableRooms.filter := format('(Map_id = %d)', [mapid]);
  datawell.TableRooms.Filtered := true;
  datawell.TableRooms.Active := true;

  if datawell.TableExits.Active then
    datawell.TableExits.ApplyUpdates(1);
  datawell.TableExits.Filter := (format('(Map1 = %d)', [mapid]));
  datawell.TableExits.Filtered := true;
  datawell.TableExits.Active := true;

  CMAP := map;
end;

procedure TFormMap.MenuZoomInClick(Sender: TObject);
var v: integer;
begin
  if listMaps.text = '' then exit;
  case TMenuItem(sender).tag of
    1: v:=50;
    2: v:=-50;
    else raise Exception.CreateFmt('TFormMap.MenuZoomInClick: Invalid tag: %d', [TMenuItem(sender).tag]);
  end;
  if (v < 0) and (dsCurrentMap.fieldbyname('Zoom').asinteger <= 100) then exit;
  if (v > 0) and (dsCurrentMap.fieldbyname('Zoom').asinteger >= 500) then exit;
  dsCurrentMap.edit;
  dsCurrentMap.fieldbyname('Zoom').asinteger:=
    dsCurrentMap.fieldbyname('Zoom').asinteger+v;
  dsCurrentMap.post;
  dsCurrentMap.ApplyUpdates(0);
  DrawMap(true);
end;

procedure TFormMap.MenuUpClick(Sender: TObject);
var which, amt: integer;
begin
  if listMaps.text = '' then exit;
  amt:=2;
  which:=TMenuItem(sender).tag;
  if which > 4 then begin
    amt:=8;
    dec(which,4);
  end;
  case which of
    1: inc(yoffset,amt);
    2: dec(xoffset,amt);
    3: dec(yoffset,amt);
    4: inc(xoffset,amt);
  end;
  DrawMap(true);
end;

procedure TFormMap.MenuDeleteMapClick(Sender: TObject);
var
  clearing: boolean;
  mapname: string;
  mapID: integer;
begin
  clearing:=(TMenuItem(sender).tag = 1);
  if listMaps.text = '' then exit;
  if (BtnChart.down) or (BtnFollow.down) then begin
    messagedlg('You must stop charting and following first',
      mtinformation,[mbok],0);
    exit;
  end;
  mapname := dsCurrentMap.fieldbyname('Map').asstring;
  if clearing then begin
    if messagedlg('Are you SURE you want to clear the map level: ' + mapname +
      '?  There is no undoing this',
      mtconfirmation,[mbyes,mbno],0) = mrno then exit;
  end
  else begin
    if messagedlg('Are you SURE you want to delete the map level: ' + mapname +
      '?  There is no undoing this',
      mtconfirmation,[mbyes,mbno],0) = mrno then exit;
  end;
  UpdateFilters(mapname);

  mapID := GetMapID(mapname);
  datawell.ExecuteArbitraryQuery(format('delete from exits where (map1 = %d) or (map2 = %d)', [mapID, mapID]));
  datawell.ExecuteArbitraryQuery(format('delete from rooms where map_id = %d', [mapID]));

  if not clearing then begin
    dsCurrentMap.delete;
    dsCurrentMap.ApplyUpdates(1);
    listMaps.text:='';
    Datawell.TableMaps.first;
    if ChangeMap(Datawell.TableGlobals.fieldbyname('ChartLastMap').asstring) then exit;
    if dsCurrentMap.fieldbyname('Map').asstring = '' then begin
      WipeMap;
      exit;
    end;
    if not ChangeMap(Datawell.TableMaps.fieldbyname('Map').asstring) then WipeMap;
  end
  else begin
    with Datawell.TableMaps do begin
      edit;
      fieldbyname('HomeX').asinteger:=-((FormMap.width-50)*5) div (fieldbyname('Zoom').asinteger*2);
      fieldbyname('HomeY').asinteger:=((FormMap.height-100)*5) div (fieldbyname('Zoom').asinteger*2);
      post;
    end;
    BtnHome.click;
  end;
  datawell.TableMaps.ApplyUpdates(1);
  //Clear undos
  Undos.clear;
  MenuUndo.enabled:=false;
end;

procedure TFormMap.CreateRoom(map: string; x, y, kind, colfg, colbg: integer;
  slabel, stag, swarn, stubs: string; overwrite: boolean);
var
  sameroom: boolean;
  table: TClientDataset;
begin
  sameroom:=true;
  table:=TClientDataset.create(self);
  with table do begin
    CloneCursor(Datawell.TableRooms, false);
    if table.Locate('map_id;x;y', VarArrayOf([GetMapID(map), x, y]), []) then
    begin
      if not overwrite then
      begin
        slabel:=fieldbyname('Label').asstring;
        stag:=fieldbyname('Tag').asstring;
        swarn:=fieldbyname('Warn').asstring;
        stubs:=fieldbyname('Stubs').asstring;
      end;
      if fieldbyname('MAP_ID').AsInteger <> dsCurrentMap.fieldbyname('MAP_ID').asInteger then
        sameroom:=false
      else if fieldbyname('Kind').asinteger <> kind then
        sameroom:=false
      else if fieldbyname('Color').asinteger <> colfg then
        sameroom:=false
      else if fieldbyname('BColor').asinteger <> colbg then
        sameroom:=false
      else if fieldbyname('Label').asstring <> slabel then
        sameroom:=false
      else if fieldbyname('Tag').asstring <> stag then
        sameroom:=false
      else if fieldbyname('Warn').asstring <> swarn then
        sameroom:=false;
{      else if fieldbyname('Stubs').asstring <> stubs then
        sameroom:=false;}
      if sameroom then
      begin
        if not overrideroom then
          dec(undoid);
        table.free;
        exit;
      end;
      Edit;
    end
    else begin
      Append;
      fieldByName('x').AsInteger := x;
      fieldByName('y').AsInteger := y;
      fieldByName('Room_id').AsInteger := GetNewID;
    end;
    fieldbyname('MAP_ID').AsInteger := GetMapID(map);
    fieldbyname('Kind').asinteger:=kind;
    fieldbyname('Color').asinteger:=colfg;
    fieldbyname('BColor').asinteger:=colbg;
    fieldbyname('Label').asstring:=slabel;
    fieldbyname('Tag').asstring:=stag;
    fieldbyname('Warn').asstring:=swarn;
    fieldbyname('Stubs').asstring:=stubs;
    post;
    AddHistory('Room|'+map+'|'+inttostr(x)+'|'+inttostr(y)+'|'+
      inttostr(kind)+'|'+inttostr(colfg)+'|'+inttostr(colbg)+'|'+
      slabel+'|'+stag+'|'+swarn+'|'+stubs);
  end;
  table.free;
  datawell.tableRooms.ApplyUpdates(1);
end;

function TFormMap.DeleteRoom(map: string; x, y: integer; dexits, manual: boolean): boolean;
const EXIT_FILTER_STR = '(ROOM1 = %d) or (ROOM2 = %d)';
var
  table: TCustomClientDataset;
  mapID, roomID: integer;
begin
  mapID := GetMapID(map);
  if not undoing then MenuClearSelections.click;
  //(MAP_ID = %d) and (X = %d) and (Y = %d)
  table := Datawell.TableRooms;
  result := table.Locate('MAP_ID;X;Y', VarArrayOf([mapID, x, y]), []);
  if result then begin
    AddHistory('-Room|'+map+'|'+inttostr(x)+'|'+inttostr(y)+'|'+
      table.fieldbyname('Kind').AsString+'|'+
      table.fieldbyname('Color').AsString+'|'+
      table.fieldbyname('BColor').AsString+'|'+
      table.fieldbyname('Label').asstring+'|'+
      table.fieldbyname('Tag').asstring+'|'+
      table.fieldbyname('Warn').asstring+'|'+
      table.fieldbyname('Stubs').asstring);
    roomID := table.fieldByName('room_id').AsInteger;
    table.delete;
    table := Datawell.TableExits;
    table.filter := format(EXIT_FILTER_STR, [roomID, roomID]);
    table.filtered := true;
    try
      table.first;
      while not table.EOF do
        table.Delete;
    finally
      table.filtered := false;
    end;
  end;
  Datawell.TableExits.ApplyUpdates(1);
  Datawell.TableRooms.ApplyUpdates(-1);
  DrawMap(false);
end;

procedure TFormMap.BtnUndoClick(Sender: TObject);
var
  id, p, x1, y1, x2, y2, kind, rkind, colf, colb: integer;
  pre, post, map1, map2, work, undo, dir, slabel, stag, swarn, stubs: string;
  deleting, linked: boolean;
begin
  if listMaps.text = '' then exit;
  if PanelSelection.visible then exit;
  if Undos.count < 1 then exit;
  deleting:=false;
  linked:=false;
  kind:=0; //1=Room, 2=Link, 3=Room move
  undo:=Undos[0];
  p:=pos('|',undo);
  id:=Datawell.toint(copy(undo,1,p-1));
  delete(undo,1,p);
  while id = undoid do begin
    if copy(undo,1,1) = '-' then begin
      deleting:=true;
      delete(undo,1,1);
    end;
    work:=copy(undo,1,4);
    if work = 'Room' then kind:=1
    else if work = 'Exit' then kind:=2
    else if work = 'Move' then kind:=3;
    if deleting then inc(kind,100);
    x1 := 0; y1 := 0; colf := 0; x2 := 0; y2 := 0; rkind := 0; colb := 0;
    delete(undo,1,5);
    if (kind = 1) or (kind = 101) then begin
      p:=pos('|',undo);
      map1:=copy(undo,1,p-1);
      delete(undo,1,p);
      p:=pos('|',undo);
      x1:=Datawell.toint(copy(undo,1,p-1));
      delete(undo,1,p);
      p:=pos('|',undo);
      y1:=Datawell.toint(copy(undo,1,p-1));
      delete(undo,1,p);
      p:=pos('|',undo);
      rkind:=Datawell.toint(copy(undo,1,p-1));
      delete(undo,1,p);
      p:=pos('|',undo);
      colf:=Datawell.toint(copy(undo,1,p-1));
      delete(undo,1,p);
      p:=pos('|',undo);
      colb:=Datawell.toint(copy(undo,1,p-1));
      delete(undo,1,p);
      p:=pos('|',undo);
      slabel:=copy(undo,1,p-1);
      delete(undo,1,p);
      p:=pos('|',undo);
      stag:=copy(undo,1,p-1);
      delete(undo,1,p);
      p:=pos('|',undo);
      swarn:=copy(undo,1,p-1);
      delete(undo,1,p);
      stubs:=undo;
    end
    else if (kind = 2) or (kind = 102) then begin
      p:=pos('|',undo);
      map1:=copy(undo,1,p-1);
      delete(undo,1,p);
      p:=pos('|',undo);
      map2:=copy(undo,1,p-1);
      delete(undo,1,p);
      p:=pos('|',undo);
      pre:=copy(undo,1,p-1);
      delete(undo,1,p);
      p:=pos('|',undo);
      post:=copy(undo,1,p-1);
      delete(undo,1,p);
      p:=pos('|',undo);
      x1:=Datawell.toint(copy(undo,1,p-1));
      delete(undo,1,p);
      p:=pos('|',undo);
      y1:=Datawell.toint(copy(undo,1,p-1));
      delete(undo,1,p);
      p:=pos('|',undo);
      x2:=Datawell.toint(copy(undo,1,p-1));
      delete(undo,1,p);
      p:=pos('|',undo);
      y2:=Datawell.toint(copy(undo,1,p-1));
      delete(undo,1,p);
      p:=pos('|',undo);
      dir:=copy(undo,1,p-1);
      delete(undo,1,p);
      colf:=Datawell.toint(undo);
    end
    else if kind = 3 then begin
      p:=pos('|',undo);
      map1:=copy(undo,1,p-1);
      delete(undo,1,p);
      p:=pos('|',undo);
      x1:=Datawell.toint(copy(undo,1,p-1));
      delete(undo,1,p);
      p:=pos('|',undo);
      y1:=Datawell.toint(copy(undo,1,p-1));
      delete(undo,1,p);
      p:=pos('|',undo);
      x2:=Datawell.toint(copy(undo,1,p-1));
      delete(undo,1,p);
      y2:=Datawell.toint(undo);
    end;
    if kind > 0 then begin
      Undos.delete(0);
      linked:=true;
    end;
    case kind of
        1: begin
             undoing:=true;
             DeleteRoom(map1,x1,y1,false,false);
             undoing:=false;
           end;
        2: begin
             undoing:=true;
             DeleteLinkExits(GetMapID(map1),GetMapID(map2),x1,y1,x2,y2,true);
             undoing:=false;
           end;
      101: begin
             undoing:=true;
             CreateRoom(map1,x1,y1,rkind,colf,colb,slabel,stag,swarn,stubs,true);
             undoing:=false;
           end;
      102: begin
             undoing:=true;
             CreateLinkExits(map1,map2,dir,'',pre,post,'','',x1,y1,x2,y2,colf,true);
             undoing:=false;
           end;
        3: begin
             undoing:=true;
             MoveRoom(map1,x2,y2,x1,y1);
             undoing:=false;
           end;
    end;
    if linked then DrawMap(false);
    if Undos.count < 1 then begin
      MenuUndo.enabled:=false;
      undoid:=1;
      break;
    end;
    undo:=Undos[0];
    p:=pos('|',undo);
    id:=Datawell.toint(copy(undo,1,p-1));
    delete(undo,1,p);
    UpdateFilters(map1);
  end;
  ChangeMap(map1);
  dec(undoid);
  if BtnChart.down then begin
    if mapundostr <> '' then begin
      p:=pos('|',mapundostr);
      map1:=copy(mapundostr,1,p-1);
      delete(mapundostr,1,p);
      p:=pos('|',mapundostr);
      x1:=Datawell.toint(copy(mapundostr,1,p-1));
      delete(mapundostr,1,p);
      y1:=Datawell.toint(mapundostr);
      mapundostr:='';
      SetXY(map1,x1,y1);
    end;
  end;
end;

procedure TFormMap.PopupSetLocationClick(Sender: TObject);
begin
  SetXY(dsCurrentMap.fieldbyname('Map').asstring,GX,GY);
end;

procedure TFormMap.WMEnterSizeMove;
begin
  MapCanvas.hide;
end;

procedure TFormMap.WMExitSizeMove;
begin
  MapCanvas.show;
  DrawMap(true);
end;

function TFormMap.ChangeMap(newmap: string): boolean;
const
   MAP_SELECT = '(map = %s)';
begin
  result:=true;
  if newmap = '' then exit;
  if newmap = CMAP then exit;
  result:=false;
  //Setup the map, room and link tables
  try
    UpdateFilters(newmap);
  except
    on EDatabaseError do
      Exit(false);
  end;

  dsCurrentMap.Filter := format(MAP_SELECT, [quotedStr(newmap)]);
  dsCurrentMap.filtered := true;
  if dsCurrentMap.RecordCount = 0 then exit;
  with dsCurrentMap do begin
    //Check for update
    if not fieldbyname('L5').asboolean then begin
      edit;
      fieldbyname('LL').asboolean:=false;
      fieldbyname('I1').asinteger:=360;
      fieldbyname('I2').asinteger:=clsilver;
      fieldbyname('I3').asinteger:=clteal;
      fieldbyname('I5').asinteger:=clblack;
      fieldbyname('L1').asboolean:=true;
      fieldbyname('L3').asboolean:=true;
      fieldbyname('L4').asboolean:=true;
      fieldbyname('L5').asboolean:=true;
      post;
    end;
    //Check new zoom minimum
    if fieldbyname('Zoom').asinteger < 100 then begin
      edit;
      fieldbyname('Zoom').asinteger:=100;
      post;
    end;
    //Update the map text
    listMaps.ItemIndex:=listMaps.Items.IndexOf(fieldbyname('Map').asstring);
    //Update default offset
    xoffset:=-fieldbyname('HomeX').asinteger*2;
    yoffset:=fieldbyname('HomeY').asinteger*2;
    //Update menu items
    SGrid.checked:=fieldbyname('ShowGrid').asboolean;
    SLabels.checked:=fieldbyname('ShowLabels').asboolean;
    STags.checked:=fieldbyname('ShowTags').asboolean;
    SStubs.checked:=fieldbyname('ShowStubs').asboolean;
    SLL.checked:=fieldbyname('LL').asboolean;
    SSpeed.checked:=fieldbyname('SpeedEdit').asboolean;
    SBasement.checked:=fieldbyname('L3').asboolean;
    SAttic.checked:=fieldbyname('L4').asboolean;
  end;
  //Update the backgrounds
  PanelExample.color:=dsCurrentMap.fieldbyname('BColor').asinteger;
  PanelBaseLeft.color:=dsCurrentMap.fieldbyname('BColor').asinteger;
  //Draw the new map and ruler
  MapCanvas.show;
  DrawMap(true);
  //Clear undos
  Undos.clear;
  MenuUndo.enabled:=false;
  UpdateBasementButtons;
end;

procedure TFormMap.PopupSprintClick(Sender: TObject);
var
  x, y, sprintresult: integer;
  ischarting: boolean;
  zzz: TStringList;
begin
  ischarting:=BtnChart.down;
  if (GX = CX) and (GY = CY) and
    (dsCurrentMap.fieldbyname('Map').asstring = CMAP) then begin
    messagedlg('That''s the same room - duh',mtinformation,[mbok],0);
    exit;
  end;
  x:=GX; y:=GY;
  zzz:=TStringList.create;
  if ischarting then begin
    BtnChart.down:=false;
    UpdateCharting(1,false);
  end;
  sprintresult:=Sprint(dsCurrentMap.fieldbyname('Map').asstring,x,y,false,zzz);
  if ischarting then begin
    BtnChart.down:=true;
    UpdateCharting(1,false);
  end;
  zzz.free;
  if sprintresult > 0 then begin
    FormMain.show;
    exit;
  end;
  case sprintresult of
    0: messagedlg('No path found to room ('+inttostr(x)+','+inttostr(y)+') [Map: '+
         dsCurrentMap.fieldbyname('Map').asstring+']',mtinformation,[mbok],0);
   -1: messagedlg('No link exists to room ('+inttostr(x)+','+inttostr(y)+') [Map: '+
         dsCurrentMap.fieldbyname('Map').asstring+']',mtinformation,[mbok],0);
  end;
end;

procedure TFormMap.MenuDefaultClick(Sender: TObject);
var work: string;
begin
  if listMaps.text = '' then exit;
  work:=dsCurrentMap.fieldbyname('Map').asstring;
  Datawell.TableGlobals.edit;
  Datawell.TableGlobals.fieldbyname('ChartLastMap').asstring:=work;
  Datawell.TableGlobals.post;
  Datawell.TableGlobals.ApplyUpdates(1);
end;

procedure TFormMap.MenuPropertiesClick(Sender: TObject);
begin
  if listMaps.text = '' then exit;
  if (LabelPosition.font.color <> clblack) and (TComponent(sender).tag <> 2) then exit;
  case TComponent(sender).tag of
      2: PopupProperties.click;
      3: PopupSetLocation.click;
      4: PopupSprint.click;
     12: PopupSelect.click;
    666: PopupDelete.click;
    667: PopupDelete2.click;
  end;
end;

procedure TFormMap.TimerActivatingTimer(Sender: TObject);
begin
  activating:=false;
  TimerActivating.enabled:=false;
end;

function TFormMap.IsExit(dir, map: string; x, y: integer): boolean;
var table: TClientDataset;
begin
  result:=false;
  table:=TClientDataset.create(self);
  with table do begin
    CloneCursor(Datawell.TableExits, false);
    filter := format(ROOM_FILTER_STR, [GetMapID(map), x, y]) +  'and (Exit = '''+dir+''')';
    filtered := true;
    active:=true;
    first;
    while not EOF do begin
      if fieldbyname('FExit').asstring = dir then begin
        result:=true;
        break;
      end;
      next;
    end;
  end;
  table.free;
end;

function TFormMap.RoomProperties(map: string; x, y: integer;
      var r1, r2, r3: integer; var s1, s2, s3, ss1: string): boolean;
var
  table: TCustomClientDataset;
begin
  table:=Datawell.TableRooms;
  result:=table.locate('MAP_ID;X;Y', VarArrayOf([GetMapID(map), x, y]), []);
  if result then begin
    r1:=table.fieldbyname('Kind').asinteger;
    r2:=table.fieldbyname('Color').asinteger;
    r3:=table.fieldbyname('BColor').asinteger;
    s1:=table.fieldbyname('Label').asstring;
    s2:=table.fieldbyname('Tag').asstring;
    s3:=table.fieldbyname('Warn').asstring;
    ss1:=table.fieldbyname('Stubs').asstring;
  end;
end;

procedure TFormMap.PopupCopySprintClick(Sender: TObject);
var
  x, y, sprintresult, a1m2: integer;
  coms: TStringList;
  form: TFormAM;
  ischarting: boolean;
  grab: string;
begin
  ischarting:=BtnChart.down;
  if (GX = CX) and (GY = CY) and
    (dsCurrentMap.fieldbyname('Map').asstring = CMAP) then begin
    messagedlg('That''s the same room - duh',mtinformation,[mbok],0);
    exit;
  end;
  x:=GX; y:=GY;
  form:=TFormAM.create(self);
  form.setup;
  form.showmodal;
  grab:=form.am;
  a1m2:=form.a1m2;
  form.free;
  if grab = '' then exit;
  coms:=TStringList.create;
  if ischarting then begin
    BtnChart.down:=false;
    UpdateCharting(1,false);
  end;
  sprintresult:=Sprint(dsCurrentMap.fieldbyname('Map').asstring,x,y,true,coms);
  if ischarting then begin
    BtnChart.down:=true;
    UpdateCharting(1,false);
  end;
  if sprintresult > 0 then CopySprint(a1m2,x,y,grab,coms)
  else begin
    case sprintresult of
      0: messagedlg('No path found to room ('+inttostr(x)+','+inttostr(y)+') [Map: '+
           dsCurrentMap.fieldbyname('Map').asstring+']',mtinformation,[mbok],0);
     -1: messagedlg('No link exists to room ('+inttostr(x)+','+inttostr(y)+') [Map: '+
           dsCurrentMap.fieldbyname('Map').asstring+']',mtinformation,[mbok],0);
    end;
  end;
  coms.free;
end;

procedure TFormMap.FilterStubs(map: string; x, y: integer);
var
  roomtable, table: TClientDataset;
  d1, d2, d3, d4, d5, d6, d7, d8, d9: boolean;
  work, ex: string;
begin
  d1:=false; d2:=false; d3:=false; d4:=false; d5:=false;
  d6:=false; d7:=false; d8:=false; d9:=false;
  roomtable:=TClientDataset.create(self);
  with roomtable do begin
    CloneCursor(Datawell.TableRooms, false);
    filter := format(ROOM_FILTER_STR, [GetMapID(map), x, y]);
    filtered := true;
    active:=true;
    first;
  end;
  if roomtable.EOF then begin
    roomtable.free;
    exit;
  end;
  work:=roomtable.fieldbyname('Stubs').asstring;
  if pos('1',work) > 0 then d1:=true;
  if pos('2',work) > 0 then d2:=true;
  if pos('3',work) > 0 then d3:=true;
  if pos('4',work) > 0 then d4:=true;
  if pos('5',work) > 0 then d5:=true;
  if pos('6',work) > 0 then d6:=true;
  if pos('7',work) > 0 then d7:=true;
  if pos('8',work) > 0 then d8:=true;
  if pos('9',work) > 0 then d9:=true;
  table:=TClientDataset.create(self);
  with table do begin
    CloneCursor(Datawell.TableExits, false);
    filter := format(EXIT_FILTER_STR, [GetMapID(map), x, y]);
    filtered := true;
    active:=true;
    first;
    while not EOF do begin
      ex:=fieldbyname('FExit').asstring;
      if (ex = Datawell.Dir('n')) or (ex = Datawell.Dir('north')) then d8:=false
      else if (ex = Datawell.Dir('ne')) or (ex = Datawell.Dir('northeast')) then d9:=false
      else if (ex = Datawell.Dir('e')) or (ex = Datawell.Dir('east')) then d6:=false
      else if (ex = Datawell.Dir('se')) or (ex = Datawell.Dir('southeast')) then d3:=false
      else if (ex = Datawell.Dir('s')) or (ex = Datawell.Dir('south')) then d2:=false
      else if (ex = Datawell.Dir('sw')) or (ex = Datawell.Dir('southwest')) then d1:=false
      else if (ex = Datawell.Dir('w')) or (ex = Datawell.Dir('west')) then d4:=false
      else if (ex = Datawell.Dir('nw')) or (ex = Datawell.Dir('northwest')) then d7:=false;
      next;
    end;
  end;
  table.free;
  work:='';
  if d1 then work:=work+'1';
  if d2 then work:=work+'2';
  if d3 then work:=work+'3';
  if d4 then work:=work+'4';
  if d5 then work:=work+'5';
  if d6 then work:=work+'6';
  if d7 then work:=work+'7';
  if d8 then work:=work+'8';
  if d9 then work:=work+'9';
  roomtable.edit;
  roomtable.fieldbyname('Stubs').asstring:=work;
  roomtable.post;
  roomtable.free;
end;


procedure TFormMap.MenuMapPropertiesClick(Sender: TObject);
var form: TFormMapProperties;
begin
  if listMaps.text = '' then exit;
  form:=TFormMapProperties.create(self);
  form.MemoMap.lines.assign(dsCurrentMap.fieldbyname('Memo'));
  form.caption:='Map Level Properties: '+dsCurrentMap.fieldbyname('Map').asstring;
  form.ColorGrid1.foregroundindex:=Datawell.ColorFigure(dsCurrentMap.fieldbyname('FColor').asinteger);
  form.ColorGrid1.backgroundindex:=Datawell.ColorFigure(dsCurrentMap.fieldbyname('BColor').asinteger);
  form.ColorGrid2.foregroundindex:=Datawell.ColorFigure(dsCurrentMap.fieldbyname('LColor').asinteger);
  form.ColorGrid3.foregroundindex:=Datawell.ColorFigure(dsCurrentMap.fieldbyname('SColor').asinteger);
  form.ColorGrid4.foregroundindex:=Datawell.ColorFigure(dsCurrentMap.fieldbyname('I3').asinteger);
  form.ColorGrid4.backgroundindex:=Datawell.ColorFigure(dsCurrentMap.fieldbyname('I2').asinteger);
  form.CheckPrint.checked:=dsCurrentMap.fieldbyname('L1').asboolean;
  form.ColorGrid5.foregroundindex:=Datawell.ColorFigure(dsCurrentMap.fieldbyname('I4').asinteger);
  form.map:=listMaps.text;
  form.EditBasement.text:=dsCurrentMap.fieldbyname('S2').asstring;
  form.EditAttic.text:=dsCurrentMap.fieldbyname('S3').asstring;
  form.setup;
  form.showmodal;
  form.free;
  //Draw the map
  DrawMap(false);
  //Update the example color
  PanelExample.color:=dsCurrentMap.fieldbyname('BColor').asinteger;
  UpdateBasementButtons;
end;

procedure TFormMap.MenuTurnAllOnClick(Sender: TObject);
var
  g: integer;
  ison: boolean;
  table: TClientDataset;
begin
  g:=TComponent(sender).tag;
  if g > 100 then begin
    dec(g,100);
    ison:=false;
  end
  else ison:=true;
  table:=TClientDataset.create(self);
  with table do begin
    CloneCursor(Datawell.TableMaps, true);
    active:=true;
    first;
    while not EOF do begin
      edit;
      case g of
        1: fieldbyname('ShowGrid').asboolean:=ison;
        2: fieldbyname('ShowLabels').asboolean:=ison;
        3: fieldbyname('ShowTags').asboolean:=ison;
        5: fieldbyname('ShowStubs').asboolean:=ison;
        7: fieldbyname('LL').asboolean:=ison;
        8: fieldbyname('SpeedEdit').asboolean:=ison;
        9: fieldbyname('L3').asboolean:=ison;
       10: fieldbyname('L4').asboolean:=ison;
      end;
      post;
      next;
    end;
  end;
  table.free;
  DrawMap(false);
end;

procedure TFormMap.CenterX;
begin
  if not ChangeMap(CMAP) then exit;
  if listMaps.text = '' then exit;
  xoffset:=2*((((width-20)*5) div (dsCurrentMap.fieldbyname('Zoom').asinteger*2))-CX);
  yoffset:=2*((((height-100)*5) div (dsCurrentMap.fieldbyname('Zoom').asinteger*2))+CY);
end;

procedure TFormMap.BtnFontClick(Sender: TObject);
begin
  DialogFont.font.size:=8;
  DialogFont.font.style:=[];
  DialogFont.font.name:=dsCurrentMap.fieldbyname('S1').asstring;
  if DialogFont.font.name = '' then DialogFont.font.name:='Arial';
  if DialogFont.execute then begin
    dsCurrentMap.edit;
    dsCurrentMap.fieldbyname('S1').asstring:=DialogFont.font.name;
    dsCurrentMap.post;
  end;
  DrawMap(false);
end;

function TFormMap.CleanRoom(map: string; x, y: integer): integer;
const
  EITHER_EXIT = '((Map1 = %d) and (x = %d) and (y = %d)) or ((Map2 = %d) and (x2 = %d) and (y2 = %d))';
var
  table: TClientDataset;
  didfind: boolean;
begin
// 1: room blocking
// 2: exit blocking
// 3: room & exit blocking
  result:=0;
//Check if there's a room currently there
  table:=TClientDataset.create(self);
  with table do begin
    CloneCursor(Datawell.TableRooms, false);
    filter := format(ROOM_FILTER_STR, [GetMapID(map), x, y]);
    filtered := true;
    active:=true;
    didfind:= not EOF;
  end;
  table.free;
  if didfind then result:=1;
//Check if there's an exit currently to or from there
  table:=TClientDataset.create(self);
  with table do begin
    CloneCursor(Datawell.TableExits, true);
    filtered := true;
    filter := format(EITHER_EXIT, [GetMapID(map), x, y, dsCurrentMap.fieldbyname('Map_id').asInteger, x, y]);
    active:=true;
    didfind := not EOF;
  end;
  table.free;
  if didfind then
  begin
    if result = 1 then
      result:=3
    else result:=2;
  end;
end;

procedure TFormMap.SelectRoom(x, y: integer);
begin
  if not RoomAt(dsCurrentMap.fieldbyname('Map').asstring,x,y) then exit;
  if Datawell.selx.indexof(inttostr(x)+'|'+inttostr(y)) >= 0 then begin
    Datawell.selx.delete(Datawell.selx.indexof(inttostr(x)+'|'+inttostr(y)));
  end
  else begin
    Datawell.selx.add(inttostr(x)+'|'+inttostr(y));
  end;
  UpdateSelection;
end;

procedure TFormMap.UpdateSelection;
var ena: boolean;
begin
  if Datawell.selx.count > 0 then begin
    MenuSelection.enabled:=true;
    PanelSelection.top:=PanelToolbar.top;
    PanelSelection.show;
    PanelToolbar.hide;
    ena:=false;
  end
  else begin
    MenuSelection.enabled:=false;
    PanelToolbar.show;
    PanelSelection.hide;
    ena:=true;
  end;
  ListMaps.enabled:=ena;
  MenuNewMap.enabled:=ena;
  MenuMapProperties.enabled:=ena;
  MenuRenameMap.enabled:=ena;
  MenuPrint.enabled:=ena;
  MenuDeleteMap.enabled:=ena;
  MenuImport.enabled:=false;
  MenuExport.enabled:=ena;
  MenuBatchExport.enabled:=ena;
  MenuDefault.enabled:=ena;
  MenuZero.enabled:=ena;
  SGrid.enabled:=ena;
  SLabels.enabled:=ena;
  STags.enabled:=ena;
  SStubs.enabled:=ena;
  SLL.enabled:=ena;
  SSpeed.enabled:=ena;
  SBasement.enabled:=ena;
  MenuTurnOffAll.enabled:=ena;
  MenuTurnOnAll.enabled:=ena;
  MenuFont.enabled:=ena;
  MenuHighlight.enabled:=ena;
  MenuCompleteStubs.enabled:=ena;
  MenuProperties.enabled:=ena;
  MenuSetLocation.enabled:=ena;
  MenuSprint.enabled:=ena;
  MenuDelete.enabled:=ena;
  MenuDelete2.enabled:=ena;
  PopupProperties.enabled:=ena;
  PopupSetLocation.enabled:=ena;
  PopupSprint.enabled:=ena;
  PopupDelete.enabled:=ena;
  PopupDelete2.enabled:=ena;
end;

procedure TFormMap.MenuClearSelectionsClick(Sender: TObject);
begin
  if listMaps.text = '' then exit;
  if Datawell.selx.count < 1 then exit;
  Datawell.selx.clear;
  DrawMap(false);
  UpdateSelection;
end;

procedure TFormMap.MenuSelectionPropertiesClick(Sender: TObject);
var
  i, x, y, p, roomfg, roombg, roomstyle: integer;
  table: TClientDataset;
  work, roomlabel, roomtag: string;
  form: TFormRProperties;
  cancelling: boolean;
begin
  if listMaps.text = '' then exit;
  if Datawell.selx.count < 1 then exit;
  if highlightstubs then exit;
  form:=TFormRProperties.create(self);
  work:=Datawell.selx[0];
  p:=pos('|',work);
  if p < 1 then exit;;
  x:=Datawell.toint(copy(work,1,p-1));
  y:=Datawell.toint(copy(work,p+1,9999));
  if not form.LoadOptions(dsCurrentMap.fieldbyname('Map').asstring,x,y,false) then begin
    form.free;
    exit;
  end
  else begin
    form.multiple:=true;
    form.EditWarn.readonly:=true;
    form.EditWarn.color:=clsilver;
    form.EditWarn.tabstop:=false;
    form.EditNotes.readonly:=true;
    form.EditNotes.color:=clsilver;
    form.EditNotes.tabstop:=false;
    form.GridExits.readonly:=true;
    form.GridExits.color:=clsilver;
    form.GridExits.tabstop:=false;
    form.BtnAdd.enabled:=false;
    form.BtnRename.enabled:=false;
    form.BtnEdit.enabled:=false;
    form.BtnDelete.enabled:=false;
    form.BtnJump.enabled:=false;
    form.BoxStubs.enabled:=false;
    form.showmodal;
    roomlabel:=form.EditLabel.text;
    roomtag:=form.EditTag.text;
    roomfg:=form.ShapeRoom.pen.color;
    roombg:=form.ShapeRoom.brush.color;
    roomstyle:=form.FigureRoom;
    cancelling:=form.cancelling;
  end;
  form.free;
  UpdateFilters(dsCurrentMap.fieldbyname('Map').asstring);
  if cancelling then exit;
//  inc(undoid);
  for i:=0 to Datawell.selx.count-1 do begin
    work:=Datawell.selx[i];
    p:=pos('|',work);
    if p < 1 then continue;
    x:=Datawell.toint(copy(work,1,p-1));
    y:=Datawell.toint(copy(work,p+1,9999));
    if not RoomAt(dsCurrentMap.fieldbyname('Map').asstring,x,y) then continue;
    table:=TClientDataset.create(self);
    with table do begin
      CloneCursor(Datawell.TableRooms, false);
      filter := format(ROOM_FILTER_STR, [GetMapID(dsCurrentMap.fieldbyname('Map').asstring), x, y]);
      filtered := true;
      active:=true;
      first;
      edit;
      fieldbyname('Label').asstring:=roomlabel;
      fieldbyname('Tag').asstring:=roomtag;
      fieldbyname('Color').asinteger:=roomfg;
      fieldbyname('BColor').asinteger:=roombg;
      fieldbyname('Kind').asinteger:=roomstyle;
      post;
    end;
    table.free;
  end;
  DrawMap(false);
end;

procedure TFormMap.NN1Click(Sender: TObject);
begin
  if listMaps.text = '' then exit;
  if Datawell.selx.count < 1 then exit;
  case TComponent(sender).tag of
    8: BtnSelectionN.click;
    6: BtnSelectionE.click;
    2: BtnSelectionS.click;
    4: BtnSelectionW.click;
  end;
end;

procedure TFormMap.MoveRoom(map: string; x, y, x2, y2: integer);
const
   QUERY = 'update ROOMS' + CRLF +
           'set x = %d, y = %d' + CRLF +
           'where (map_id = %d) and (x = %d) and (y = %d)';
var
  p: integer;
begin
  datawell.ExecuteArbitraryQuery(format(QUERY, [x2, y2, GetMapID(map), x, y]));

  p:=Datawell.selx.indexof(inttostr(x)+'|'+inttostr(y));
  if p >= 0 then Datawell.selx[p]:=inttostr(x2)+'|'+inttostr(y2);
  AddHistory('Move|'+dsCurrentMap.fieldbyname('Map').asstring+'|'+
    inttostr(x)+'|'+inttostr(y)+'|'+inttostr(x2)+'|'+inttostr(y2));
end;

procedure TFormMap.BtnSelectionNClick(Sender: TObject);
begin
  MoveSelectedRooms(TComponent(sender).tag);
end;

procedure TFormMap.MoveSelectedRooms(dir: integer);
var
  allrooms, roomsx, roomsy: TStringList;
  i, p, x, y, xchange, ychange, clean: integer;
  work: string;
  addroom: boolean;
begin
  xchange:=0;
  ychange:=0;
  clean:=0;
  if Datawell.selx.count < 1 then exit;
  allrooms:=TStringList.create;
  roomsx:=TStringList.create;
  roomsy:=TStringList.create;
  case dir of
    8: inc(ychange);
    2: dec(ychange);
    4: dec(xchange);
    6: inc(xchange);
  end;
  allrooms.assign(Datawell.selx);
  for i:=0 to allrooms.count-1 do begin
    work:=allrooms[i];
    p:=pos('|',work);
    x:=Datawell.toint(copy(work,1,p-1))+xchange;
    y:=Datawell.toint(copy(work,p+1,9999))+ychange;
    p:=allrooms.indexof(inttostr(x)+'|'+inttostr(y));
    if p >= 0 then continue;
    inc(clean,CleanRoom(dsCurrentMap.fieldbyname('Map').asstring,x,y));
  end;
  if clean > 0 then begin
    LabelStatus.caption:='There is a room and/or exit blocking the movement';
    exit;
  end;
  LabelStatus.caption:='';
  inc(undoid);
  while allrooms.count > 0 do begin
    roomsx.clear;
    roomsy.clear;
    for i:=0 to allrooms.count-1 do begin
      work:=allrooms[i];
      p:=pos('|',work);
      x:=Datawell.toint(copy(work,1,p-1));
      y:=Datawell.toint(copy(work,p+1,9999));
      addroom:=true;
      case dir of
        8: begin
             p:=roomsx.indexof(inttostr(x));
             if p >= 0 then begin
               if y > Datawell.toint(roomsy[p]) then roomsy[p]:=inttostr(y);
               addroom:=false;
             end;
           end;
        2: begin
             p:=roomsx.indexof(inttostr(x));
             if p >= 0 then begin
               if y < Datawell.toint(roomsy[p]) then roomsy[p]:=inttostr(y);
               addroom:=false;
             end;
           end;
        6: begin
             p:=roomsy.indexof(inttostr(y));
             if p >= 0 then begin
               if x > Datawell.toint(roomsx[p]) then roomsx[p]:=inttostr(x);
               addroom:=false;
             end;
           end;
        4: begin
             p:=roomsy.indexof(inttostr(y));
             if p >= 0 then begin
               if x < Datawell.toint(roomsx[p]) then roomsx[p]:=inttostr(x);
               addroom:=false;
             end;
           end;
      end;
      if addroom then begin
        roomsx.add(inttostr(x));
        roomsy.add(inttostr(y));
      end;
    end;
    for i:=0 to roomsx.count-1 do begin
      MoveRoom(dsCurrentMap.fieldbyname('Map').asstring,Datawell.toint(roomsx[i]),Datawell.toint(roomsy[i]),
        Datawell.toint(roomsx[i])+xchange,Datawell.toint(roomsy[i])+ychange);
      p:=allrooms.indexof(roomsx[i]+'|'+roomsy[i]);
      if p >= 0 then allrooms.delete(p);
    end;
  end;
  DrawMap(false);
  roomsx.free;
  roomsy.free;
  allrooms.free;
end;

procedure TFormMap.PopupSelectClick(Sender: TObject);
begin
  SelectRoom(GX,GY);
  DrawMap(false);
end;

procedure TFormMap.MenuDeselectLastClick(Sender: TObject);
begin
  if listMaps.text = '' then exit;
  if Datawell.selx.count < 1 then exit;
  Datawell.selx.delete(Datawell.selx.count-1);
  UpdateSelection;
  DrawMap(false);
end;

procedure TFormMap.CheckRoomLabel;
var
  table: TClientDataset;
  lab: string;
  mems: TStringList;
  i: integer;
begin
  mems:=TStringList.create;
  table:=TClientDataset.create(self);
  with table do begin
    CloneCursor(Datawell.TableRooms, false);
    filter := format(ROOM_FILTER_STR, [GetMapID(dsCurrentMap.fieldbyname('Map').asstring), GX, GY]);
    filtered := true;
    active:=true;
    lab:=fieldbyname('Label').asstring;
    mems.assign(fieldbyname('Memo'));
  end;
  table.free;
  if mems.count > 0 then begin
    if lab = '' then lab:='[Notes]:'
    else lab:=lab+': ';
  end;
  for i:=0 to mems.count-1 do begin
    lab:=lab+' '+mems[i];
    if length(lab) > 255 then break;
  end;
  mems.free;
  if lab = '' then exit;
  LabelStatus.color:=clteal;
  LabelStatus.font.color:=clwhite;
  LabelStatus.caption:=' '+lab;
end;

procedure TFormMap.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if Datawell.selx.count > 0 then MenuClearSelections.click;
  FormMain.UnloadScreen(12);
end;

procedure TFormMap.TimerHighlightStubsTimer(Sender: TObject);
begin
  TimerHighlightStubs.enabled:=false;
  highlightstubs:=false;
  DrawMap(false);
end;

procedure TFormMap.WipeMap;
begin
  with MapCanvas.canvas do begin
    pen.width:=1;
    pen.color:=clblack;
    brush.color:=clwhite;
    rectangle(0,0,width,height);
  end;
  with Ruler1.canvas do begin
    pen.width:=1;
    pen.color:=clblack;
    brush.color:=clsilver;
    rectangle(0,0,width,height);
  end;
  with Ruler2.canvas do begin
    pen.width:=1;
    pen.color:=clblack;
    brush.color:=clsilver;
    rectangle(0,0,width,height);
  end;
end;

procedure TFormMap.MenuCompleteStubsClick(Sender: TObject);
var
  table: TClientDataset;
  x1, y1: integer;
  mapname, stubs: string;
  form: TFormSaving;
begin
  if not Datawell.CheckRinged2('autocomplete exit stubs') then exit;
  if listMaps.text = '' then exit;
  if PanelSelection.visible then exit;
  form:=TFormSaving.create(self);
  with form do begin
    Label1.caption:='Autocompleting...';
    Label1.width:=165;
    Label1.alignment:=taCenter;
    ImageDisk.hide;
    show;
    bringtofront;
  end;
  mapname := dsCurrentMap.fieldbyname('Map').AsString;
  //Run through rooms
  table:=TClientDataset.create(self);
  with table do begin
    CloneCursor(Datawell.TableRooms, false);
    filter := 'Map_id = ' + dsCurrentMap.fieldbyname('Map_id').asString;
    filtered := true;
    active:=true;
    first;
    while not EOF do begin
      x1:=fieldbyname('X').asinteger;
      y1:=fieldbyname('Y').asinteger;
      stubs:=fieldbyname('Stubs').asstring;
      CheckStub('8',Datawell.Dir('n'),Datawell.Dir('s'),x1,y1,x1,y1+1,'2', mapname, stubs, table);
      CheckStub('9',Datawell.Dir('ne'),Datawell.Dir('sw'),x1,y1,x1+1,y1+1,'1', mapname, stubs, table);
      CheckStub('6',Datawell.Dir('e'),Datawell.Dir('w'),x1,y1,x1+1,y1,'4', mapname, stubs, table);
      CheckStub('3',Datawell.Dir('se'),Datawell.Dir('nw'),x1,y1,x1+1,y1-1,'7', mapname, stubs, table);
      CheckStub('2',Datawell.Dir('s'),Datawell.Dir('n'),x1,y1,x1,y1-1,'8', mapname, stubs, table);
      CheckStub('1',Datawell.Dir('sw'),Datawell.Dir('ne'),x1,y1,x1-1,y1-1,'9', mapname, stubs, table);
      CheckStub('4',Datawell.Dir('w'),Datawell.Dir('e'),x1,y1,x1-1,y1,'6', mapname, stubs, table);
      CheckStub('7',Datawell.Dir('nw'),Datawell.Dir('se'),x1,y1,x1-1,y1+1,'3', mapname, stubs, table);
      next;
    end;
  end;
  table.free;
  form.close;
  form.free;
  DrawMap(false);
end;

procedure TFormMap.CheckStub(stub, d1, d2: string; x1, y1, x2, y2: integer;
  antistub, map, stubs: string; table: TClientDataset);
var anti: integer;
begin
  if pos(stub,stubs) > 0 then begin
    anti:=CheckAntiStub(map,x2,y2,antistub);
    if anti > 0 then begin
      delete(stubs,pos(stub,stubs),1);
      table.edit;
      table.fieldbyname('Stubs').asstring:=stubs;
      table.post;
      if anti = 1 then d2:='';
      CreateLinkExits(map,map,d1,d2,'','','','',x1,y1,x2,y2,
        Datawell.FigureColor(ColorGrid.foregroundindex),false);
    end;
  end;
end;

function TFormMap.CheckAntiStub(map: string; x, y: integer; antistub: string): integer;
var
  table: TClientDataset;
  stubs2: string;
begin
  result:=0; //No room, and no anti stub
  table:=TClientDataset.create(self);
  with table do begin
    CloneCursor(Datawell.TableRooms, false);
    active:=true;
    EditKey;
    fieldbyname('Map_id').asInteger := GetMapID(map);
    fieldbyname('X').asinteger:=x;
    fieldbyname('Y').asinteger:=y;
    if GotoKey then begin
      stubs2:=fieldbyname('Stubs').asstring;
      if pos(antistub,stubs2) > 0 then begin
        system.delete(stubs2,pos(antistub,stubs2),1);
        edit; fieldbyname('Stubs').asstring:=stubs2; post;
        result:=2; //Room, with anti stub
      end
      else result:=1; //Room, but no anti stub
    end;
  end;
  table.free;
end;

procedure TFormMap.ListMapsDropDown(Sender: TObject);
var
  table: TClientDataset;
  map: string;
begin
  map := listMaps.text;
  ListMaps.items.clear;
  table:=TClientDataset.create(self);
  table.CloneCursor(Datawell.TableMaps, false);
  table.IndexFieldNames := 'Map';
  table.first;
  while not table.EOF do begin
    ListMaps.items.add(table.fieldbyname('Map').asstring);
    table.next;
  end;
  if (ListMaps.items.count < 1) and (sender <> self) then begin
    messagedlg('There are no map levels to open',mtinformation,[mbok],0);
    abort;
  end;
  ListMaps.itemindex:=ListMaps.items.indexof(map);
  table.free;
end;

procedure TFormMap.ListMapsCloseUp(Sender: TObject);
begin
  if bypasscloseup then exit;
  if ListMaps.itemindex < 0 then exit;
  ChangeMap(uppercase(ListMaps.Text));
end;

procedure TFormMap.MenuHighlightClick(Sender: TObject);
begin
  if not Datawell.CheckRinged2('highlight stubbed rooms') then exit;
  if listMaps.text = '' then exit;
  if PanelSelection.visible then exit;
  TimerHighlightStubs.enabled:=false;
  TimerHighlightStubs.enabled:=true;
  highlightstubs:=true;
  DrawMap(false);
end;

procedure TFormMap.BtnSetHomeClick(Sender: TObject);
begin
  if listMaps.text = '' then exit;
  if Datawell.TableGlobals.fieldbyname('ShowWarnings').asboolean then begin
    if messagedlg('Set ('+inttostr((-1) * (xoffset div 2))+','+
      inttostr((yoffset div 2))+') as the home postion?',
      mtconfirmation,[mbyes,mbcancel],0) = mrcancel then exit;
  end;
  dsCurrentMap.edit;
  dsCurrentMap.fieldbyname('HomeX').asinteger:=(-1) * (xoffset div 2);
  dsCurrentMap.fieldbyname('HomeY').asinteger:=(yoffset div 2);
  dsCurrentMap.post;
end;

procedure TFormMap.SGridClick(Sender: TObject);
begin
  if listMaps.text = '' then exit;
  TMenuItem(sender).checked:=not TMenuItem(sender).checked;
  dsCurrentMap.edit;
  dsCurrentMap.fieldbyname('ShowGrid').asboolean:=SGrid.checked;
  dsCurrentMap.fieldbyname('ShowLabels').asboolean:=SLabels.checked;
  dsCurrentMap.fieldbyname('ShowTags').asboolean:=STags.checked;
  dsCurrentMap.fieldbyname('ShowStubs').asboolean:=SStubs.checked;
  dsCurrentMap.fieldbyname('LL').asboolean:=SLL.checked;
  dsCurrentMap.fieldbyname('SpeedEdit').asboolean:=SSpeed.checked;
  dsCurrentMap.fieldbyname('L3').asboolean:=SBasement.checked;
  dsCurrentMap.fieldbyname('L4').asboolean:=SAttic.checked;
  dsCurrentMap.post;
  DrawMap(false);
end;

procedure TFormMap.fr0Click(Sender: TObject);
begin
  UpdateExample;
end;

function TFormMap.GetMapID(const mapname: string): integer;
const
  QUERY = 'select map_id ' + CRLF +
          'from maps ' + CRLF +
          'where maps.map = %s ';
begin
  if mapname = '' then
     Exit(-1);
  if not FMapDic.TryGetValue(mapname, result) then
  begin
    datawell.ArbitraryQuery.Close;
    datawell.ArbitraryQuery.SQL.Text := format(QUERY, [quotedStr(upperCase(mapname))]);
    datawell.ArbitraryQuery.Open;
    if datawell.ArbitraryQuery.EOF then
      raise EDatabaseError.Create('Map not found');
    result := datawell.ArbitraryQuery.FieldByName('map_id').AsInteger;
    datawell.ArbitraryQuery.Close;
    FMapDic.Add(mapname, result);
  end;
end;

function TFormMap.GetMapName(mapid: integer): string;
const
  QUERY = 'select map from MAPS where map_id = %d';
begin
  datawell.ArbitraryQuery.Close;
  datawell.ArbitraryQuery.SQL.Text := format(QUERY, [mapid]);
  datawell.ArbitraryQuery.Open;
  if datawell.ArbitraryQuery.EOF then
    raise EDatabaseError.Create('Map not found');
  result := datawell.ArbitraryQuery.FieldByName('map').AsString;
  datawell.ArbitraryQuery.Close;
end;

function TFormMap.GetNewID: integer;
const
  QUERY = 'select max(A.id) as ID from (' + CRLF +
          '  select max(MAP_ID) as ID from MAPS' + CRLF +
          '  union all select max(ROOM_ID) from ROOMS' + CRLF +
          '  union all select max(EXIT_ID) from EXITS ) A';
begin
  if FNewIDCounter = 0 then
  begin
    datawell.ArbitraryQuery.Close;
    datawell.ArbitraryQuery.SQL.Text := QUERY;
    datawell.ArbitraryQuery.Open;
    assert(not datawell.ArbitraryQuery.EOF);
    FNewIDCounter := datawell.ArbitraryQuery.FieldByName('ID').AsInteger;
    datawell.ArbitraryQuery.Close;
  end;
  inc(FNewIDCounter);
  result := FNewIDCounter
end;

function TFormMap.GetRoomID(const mapname: string; x, y: integer): integer;
const
  QUERY = 'select room_id ' + CRLF +
          'from rooms ' + CRLF +
          'inner join maps on maps.map_id = rooms.map_id ' + CRLF +
          'where maps.map = %s ' + CRLF +
          'and rooms.x = %d' + CRLF +
          'and rooms.y = %d';
  KEY_STR = '%s-%d-%d';
var
  key: string;
begin
  key := format(KEY_STR, [mapname, x, y]);
  if not FRoomDic.TryGetValue(key, result) then
  begin
    datawell.ArbitraryQuery.Close;
    datawell.ArbitraryQuery.SQL.Text := format(QUERY, [quotedStr(mapname), x, y]);
    datawell.ArbitraryQuery.Open;
    if datawell.ArbitraryQuery.EOF then
      raise EDatabaseError.Create('Room not found');
    result := datawell.ArbitraryQuery.FieldByName('room_id').AsInteger;
    datawell.ArbitraryQuery.Close;
    FRoomDic.Add(key, result);
  end;
end;

procedure TFormMap.UpdateExample;
var x: TImage;
begin
  ShapeRoom.pen.color:=Datawell.FigureColor(ColorGrid.foregroundindex);
  ShapeRoom.brush.color:=Datawell.FigureColor(ColorGrid.backgroundindex);
  ShapeLink.pen.color:=ShapeRoom.pen.color;
  if fr0.down then ImageStyle.hide
  else begin
    if fr1.down then x:=Style1
    else if fr2.down then x:=Style2
    else if fr3.down then x:=Style3
    else if fr4.down then x:=Style4
    else if fr5.down then x:=Style5
    else raise Exception.Create('TFormMap.UpdateExample: No required button is down.');
    with ImageStyle.canvas do begin
      brush.color:=ShapeRoom.brush.color;
      BrushCopy(Bounds(0,0,12,12),x.picture.bitmap,Bounds(0,0,12,12),clwhite);
      brush.color:=ShapeRoom.pen.color;
      floodfill(6,6,clblack,fssurface);
      if fr4.down then begin
        pixels[2,3]:=ShapeRoom.pen.color;
        pixels[3,4]:=ShapeRoom.pen.color;
        pixels[7,3]:=ShapeRoom.pen.color;
        pixels[8,2]:=ShapeRoom.pen.color;
        pixels[8,7]:=ShapeRoom.pen.color;
        pixels[9,8]:=ShapeRoom.pen.color;
        pixels[4,8]:=ShapeRoom.pen.color;
        pixels[3,9]:=ShapeRoom.pen.color;
      end;
    end;
    ImageStyle.show;
  end;
end;

procedure TFormMap.SetPrinterDPI1Click(Sender: TObject);
var form: TFormPrinterDPI;
begin
  form:=TFormPrinterDPI.create(self);
  form.SpinDPI.value:=dsCurrentMap.fieldbyname('I1').asinteger;
  form.showmodal;
  form.free;
end;

procedure TFormMap.UpdateCaption;
var cap, a, b: string;
begin
  cap:='Cartographer';
  if FormMain.MenuAnchorMap.checked then cap:='['+cap+']';
  cap:=cap+' - [Rooms: '+inttostr(grooms)+'  Links: '+inttostr(glinks)+']';
  a:=dsCurrentMap.fieldbyname('S3').asstring;
  b:=dsCurrentMap.fieldbyname('S2').asstring;
  if a = '' then a:='none';
  if b = '' then b:='none';
  cap:=cap+' [Attic: '+a+'  Basement: '+b+']';
  if caption <> cap then caption:=cap;
end;

procedure TFormMap.UpdateStubs;
begin
  with ImageStubs.canvas do begin
    brush.color:=clbtnface;
    pen.color:=clbtnface;
    pen.width:=1;
    //Clear stubs
    rectangle(0,0,22,20);
    pen.color:=clblack;
    //Draw outline
    rectangle(0,0,19,16);
    //Draw stubs
    brush.color:=clblack;
    if pos('8',currentstubs) > 0 then
      polygon([point(7,2),point(8,2),point(8,3),point(7,3)]);
    if pos('2',currentstubs) > 0 then
      polygon([point(7,12),point(8,12),point(8,13),point(7,13)]);
    if pos('4',currentstubs) > 0 then
      polygon([point(2,7),point(3,7),point(3,8),point(2,8)]);
    if pos('6',currentstubs) > 0 then
      polygon([point(12,7),point(13,7),point(13,8),point(12,8)]);
    if pos('7',currentstubs) > 0 then begin
      pixels[2,2]:=clblack;
      pixels[4,3]:=clblack;
      pixels[3,2]:=clblack;
      pixels[3,3]:=clblack;
      pixels[2,3]:=clblack;
      pixels[3,4]:=clblack;
    end;
    if pos('3',currentstubs) > 0 then begin
      pixels[12,11]:=clblack;
      pixels[13,12]:=clblack;
      pixels[12,12]:=clblack;
      pixels[13,13]:=clblack;
      pixels[11,12]:=clblack;
      pixels[12,13]:=clblack;
    end;
    if pos('9',currentstubs) > 0 then begin
      pixels[11,3]:=clblack;
      pixels[12,2]:=clblack;
      pixels[12,3]:=clblack;
      pixels[13,2]:=clblack;
      pixels[12,4]:=clblack;
      pixels[13,3]:=clblack;
    end;
    if pos('1',currentstubs) > 0 then begin
      pixels[2,12]:=clblack;
      pixels[3,11]:=clblack;
      pixels[2,13]:=clblack;
      pixels[3,12]:=clblack;
      pixels[3,13]:=clblack;
      pixels[4,12]:=clblack;
    end;
    if pos('5',currentstubs) > 0 then begin
      pixels[15,3]:=clblack;
      polygon([point(14,4),point(16,4),point(16,5),point(14,5)]);
      pixels[15,6]:=clblack;
    end;
    //Draw room
    brush.color:=clwhite;
    rectangle(4,4,12,12);
  end;
end;

procedure TFormMap.BtnSprintClick(Sender: TObject);
var
  x, y, sprintresult, a1m2: integer;
  form: TFormSetCoordinates;
  start, doingalias, ischarting: boolean;
  map, grab: string;
  commands: TStringList;
  form2: TFormAM;
begin
  if not Datawell.CheckRinged(false,'copy sprint results to alias/macro') then exit;
  ischarting:=BtnChart.down;
  if not RoomAt(CMAP,CX,CY) then begin
    messagedlg('You need to choose a current location first',mtinformation,[mbok],0);
    exit;
  end;
  form:=TFormSetCoordinates.create(self);
  form.EditX.value:=CX;
  form.EditY.value:=CY;
  form.EditMap.text:=CMAP;
  if TSpeedButton(sender).tag = 1 then begin
    form.caption:='Select Room to Record Sprinting To';
    doingalias:=true;
  end
  else begin
    form.caption:='Select Room to Sprint To';
    doingalias:=false;
  end;
  form.EditMap.itemindex:=(form.EditMap.items.indexof(CMAP));
  form.showmodal;
  x:=trunc(form.EditX.value);
  y:=trunc(form.EditY.value);
  map:=form.EditMap.text;
  start:=form.start;
  form.free;
  if not start then begin
    FormMain.show;
    exit;
  end;
  if (x = CX) and (y = CY) and
    (map = CMAP) then begin
    messagedlg('That''s the same room - duh',mtinformation,[mbok],0);
    exit;
  end;
  if doingalias then begin
    form2:=TFormAM.create(self);
    form2.setup;
    form2.showmodal;
    grab:=form2.am;
    a1m2:=form2.a1m2;
    form2.free;
    if grab = '' then begin
      FormMain.show;
      exit;
    end;
  end
  else a1m2 := 0;
  commands:=TStringList.create;
  if ischarting then begin
    BtnChart.down:=false;
    UpdateCharting(1,false);
  end;
  sprintresult:=Sprint(map,x,y,doingalias,commands);
  if ischarting then begin
    BtnChart.down:=true;
    UpdateCharting(1,false);
  end;
  if sprintresult <= 0 then begin
    case sprintresult of
      -1: messagedlg('No link exists to room ('+inttostr(x)+','+inttostr(y)+
            ') [Map: '+map+']',mtinformation,[mbok],0);
       0: messagedlg('No path found to room ('+inttostr(x)+','+inttostr(y)+
            ') [Map: '+map+']',mtinformation,[mbok],0);
    end;
  end;
  if doingalias then CopySprint(a1m2,x,y,grab,commands)
  else if sprintresult > 0 then FormMain.show;
  commands.free;
end;

const
   DEST_FILTER_STR = '(Map2 = ''%s'') and (X2 = %d) and (Y2 = %d)';

function TFormMap.Sprint(map: string; x, y: integer; docopy: boolean;
  var commands: TStringList): integer;
var
  exits: TClientDataset;
  i, radius: integer;
  lastradius, alldone, wasfollowing: boolean;
begin
  result:=0;
  //First see if there's even a link leading to that location
  exits:=TClientDataset.create(self);
  with exits do begin
    CloneCursor(Datawell.TableExits, false);
    filter := format(DEST_FILTER_STR, [map, x, y]);
    filtered := true;
    active:=true;
    if EOF then result:=-1;
  end;
  exits.free;
  if result = -1 then exit;
  //Begin Sprint
  sprinttomap:=map;
  sprinttox:=x;
  sprinttoy:=y;
  checkedxy:=TStringList.create;
  ds:=TStringList.create;
  nextmap:=TStringList.create;
  nextx:=TStringList.create;
  nexty:=TStringList.create;
  currentmap:=TStringList.create;
  currentx:=TStringList.create;
  currenty:=TStringList.create;
  nextmap.add(CMAP);
  nextx.add(inttostr(CX));
  nexty.add(inttostr(CY));
  checkedxy.add(CMAP+','+inttostr(CX)+','+inttostr(CY));
  ds.add('0');
  lastradius:=false;
  radius:=1;
  while (nextx.count > 0) and (not lastradius) do begin
    currentmap.assign(nextmap);
    currentx.assign(nextx);
    currenty.assign(nexty);
    nextmap.clear;
    nextx.clear;
    nexty.clear;
    for i:=0 to (currentx.count-1) do begin
      if AssignDistance(currentmap[i],strtoint(currentx[i]),strtoint(currenty[i]),radius) then
        lastradius:=true;
    end;
    inc(radius);
  end;
  if lastradius then begin
    currentmap.assign(nextmap);
    currentx.assign(nextx);
    currenty.assign(nexty);
    for i:=0 to (currentx.count-1) do
      AssignDistance(currentmap[i],strtoint(currentx[i]),strtoint(currenty[i]),radius);
    result:=1;
  end;
  nextmap.free;
  nextx.free;
  nexty.free;
  currentmap.free;
  currentx.free;
  currenty.free;
  if not lastradius then exit;
  sprintcoms.clear;
  alldone:=false;
  while not alldone do alldone:=PopulateSprintCommands(sprinttomap,sprinttox,sprinttoy);
  checkedxy.free;
  ds.free;
  ultimatemap:=map;
  ultimatex:=x;
  ultimatey:=y;
  if docopy then commands.assign(sprintcoms)
  else begin
    wasfollowing:=BtnFollow.down;
    BtnFollow.down:=false;
    SetXY(map,x,y);
    for i:=0 to sprintcoms.count-1 do FormMain.ExternalParse(sprintcoms[i],11);
    if map <> listMaps.text then ChangeMap(map);
    BtnFollow.down:=wasfollowing;
    DrawMap(false);
  end;
  sprintcoms.clear;
end;

function TFormMap.AssignDistance(map: string; x, y, d: integer): boolean;
var
  exits: TClientDataset;
begin
  //Check if it's the end room
  result:=((sprinttomap = map) and (sprinttox = x) and (sprinttoy = y));
  //Run through all exits from the room
  exits:=TClientDataset.create(self);
  with exits do begin
    CloneCursor(Datawell.TableExits, false);
    filter := format(ROOM_FILTER_STR, [GetMapID(map), x, y]);
    filtered := true;
    active:=true;
    first;
    while not EOF do begin
      //Check if the room has been already checked, if so ignore
      if checkedxy.indexof(fieldbyname('MAP2').asstring+','+
        inttostr(fieldbyname('X2').asinteger)+','+
        inttostr(fieldbyname('Y2').asinteger)) < 0 then begin
        //Add the room to the next rooms to be checked
        nextmap.add(fieldbyname('MAP2').asstring);
        nextx.add(inttostr(fieldbyname('X2').asinteger));
        nexty.add(inttostr(fieldbyname('Y2').asinteger));
        //Mark the room as already checked
        checkedxy.add(fieldbyname('MAP2').asstring+','+
          inttostr(fieldbyname('X2').asinteger)+','+
          inttostr(fieldbyname('Y2').asinteger));
        ds.add(inttostr(d));
      end;
      next;
    end;
  end;
  exits.free;
end;

function TFormMap.PopulateSprintCommands(map: string; x, y: integer): boolean;
var
  exits: TClientDataset;
  d, tempd, i: integer;
  cpost, com, cpre, warn: string;
begin
  exits:=TClientDataset.create(self);
  //Check all the exits leading into this room
  with exits do begin
    CloneCursor(Datawell.TableExits, false);
    filter := format(DEST_FILTER_STR, [map, x, y]);
    filtered := true;
    active:=true;
    first;
    d:=999999; cpost:=''; warn:=''; com:=''; cpre:='';
    while not EOF do begin
      i:=checkedxy.indexof(fieldbyname('Map').asstring+','+
        inttostr(fieldbyname('X').asinteger)+','+
        inttostr(fieldbyname('Y').asinteger));
      //Only continue if the room on the other end of the link exists
      if i >= 0 then begin
        tempd:=strtoint(ds[i]);
        if tempd < d then begin
          d:=tempd;
          cpost:=fieldbyname('PostAction').asstring;
          com:=fieldbyname('FExit').asstring;
          cpre:=fieldbyname('PreAction').asstring;
          warn:=WarningRoom(fieldbyname('Map2').asstring,
            fieldbyname('X2').asinteger,fieldbyname('Y2').asinteger);
          if warn <> '' then begin
            warn:='|WARN|'+fieldbyname('Map2').asstring+'|'+
            inttostr(fieldbyname('X2').asinteger)+'|'+
            inttostr(fieldbyname('Y2').asinteger)+'|'+
            fieldbyname('Map').asstring+'|'+
            inttostr(fieldbyname('X').asinteger)+'|'+
            inttostr(fieldbyname('Y').asinteger)+'|'+warn;
          end;
          sprinttomap:=fieldbyname('Map').asstring;
          sprinttox:=fieldbyname('X').asinteger;
          sprinttoy:=fieldbyname('Y').asinteger;
        end;
      end;
      next;
    end;
    if cpost <> '' then sprintcoms.insert(0,cpost);
    sprintcoms.insert(0,com);
    if warn <> '' then sprintcoms.insert(0,warn);
    if cpre <> '' then sprintcoms.insert(0,cpre);
  end;
  result:=(d = 0);
  exits.free;
end;

procedure TFormMap.SetXY(map: string; x, y: integer);
begin
  CX:=x;
  CY:=y;
  CMAP:=map;
  if (MenuCenterChart.checked) and (iscreated) then CenterX;
  DrawMap(true);
end;

procedure TFormMap.BtnChartClick(Sender: TObject);
begin
  UpdateCharting(TSpeedButton(sender).tag,false);
end;

procedure TFormMap.UpdateCharting(which: integer; dontshow: boolean);
var
  form: TFormSetCoordinates;
  start: boolean;
  x, y: integer;
  map: string;
begin
  if (not visible) and (not dontshow) then show;
  BtnSprint.enabled:=true;
  if (BtnChart.down) or (BtnFollow.down) then begin
    if listMaps.text = '' then begin
      messagedlg('You need to open a map level first',mtinformation,[mbok],0);
      BtnChart.down:=false;
      BtnFollow.down:=false;
      exit;
    end;
  end;
  if (BtnFollow.down) and (not RoomAt(CMAP,CX,CY)) then begin
    messagedlg('You need to choose a current location first',mtinformation,[mbok],0);
    BtnFollow.down:=false;
    exit;
  end;
  if BtnChart.down then begin
    if MenuSpeedChart.checked then begin
      x:=CX; y:=CY;
      map:=CMAP;
      start:=true;
    end
    else begin
      form:=TFormSetCoordinates.create(self);
      form.EditX.value:=CX;
      form.EditY.value:=CY;
      form.caption:='Select Starting Coordinates';
      form.EditMap.itemindex:=(form.EditMap.items.indexof(CMAP));
      form.showmodal;
      x:=trunc(form.EditX.value);
      y:=trunc(form.EditY.value);
      map:=form.EditMap.text;
      start:=form.start;
      form.free;
    end;
    if start then begin
      ChangeMap(map);
      SetXY(map,x,y);
      FormMain.systemtext('Start charting map level ['+CMAP+
        '] from room ('+inttostr(CX)+','+inttostr(CY)+')',0);
      UpdateStubs;
    end
    else begin
      BtnChart.down:=false;
      BtnSprint.enabled:=true;
    end;
  end
  else if BtnFollow.down then FormMain.systemtext('Following mode enabled',0)
  else begin
    StopCharting;
    if which = 1 then FormMain.systemtext('Charting stopped',0)
    else if which = 2 then FormMain.systemtext('Following stopped',0);
  end;
  Datawell.charting:=BtnChart.down;
  Datawell.following:=BtnFollow.down;
  if (Datawell.charting) or (Datawell.following) then FormMain.show;
end;

procedure TFormMap.StopCharting;
begin
  LabelClock.caption:=inttostr(Datawell.TableGlobals.fieldbyname('FailureTimer').asinteger);
  LabelClock.font.color:=clblack;
  LabelClock.color:=clblack;
  BtnChart.down:=false;
  HaltFailure;
end;

procedure TFormMap.HaltFailure;
begin
  awaitingfailure:=false;
  refreshingroom:=false;
  FormMain.SetupEvents;
end;

function TFormMap.FigureRoom: integer;
begin
  result:=-1;
  if fr0.down then result:=0
  else if fr1.down then result:=1
  else if fr2.down then result:=2
  else if fr3.down then result:=3
  else if fr4.down then result:=4
  else if fr5.down then result:=5;
end;

procedure TFormMap.TimerCountdownTimer(Sender: TObject);
begin
  if LabelClock.caption = '1' then begin
    FinalizeLink(false);
    exit;
  end;
  LabelClock.caption:=inttostr(Datawell.toint(LabelClock.caption)-1);
end;

procedure TFormMap.FinalizeLink(forced: boolean);
var lstr, lstr2: string;
begin
  TimerCountdown.enabled:=false;
  LabelClock.caption:=inttostr(Datawell.TableGlobals.fieldbyname('FailureTimer').asinteger);
  LabelClock.font.color:=clblack;
  LabelClock.color:=clblack;
  inc(undoid);
  overrideroom:=true;
  if awaitinglabel <> '' then s4:=awaitinglabel;
  if refreshingroom then begin
    if room2 then
      CreateRoom(gmap2,x2,y2,r4,r5,r6,s4,s5,s6,currentstubs,true);
    FormMain.systemtext('Room refreshed',0);
    refreshingroom:=false;
  end
  else begin
    if room1 then CreateRoom(gmap1,x1,y1,r1,r2,r3,s1,s2,s3,ss1,true);
    if room2 then CreateRoom(gmap2,x2,y2,r4,r5,r6,s4,s5,s6,currentstubs,true);
    if currentstubs <> '' then begin
      if (link1 = Datawell.Dir('n'))  and (pos('2',currentstubs) < 1) then link2:='';
      if (link1 = Datawell.Dir('ne')) and (pos('1',currentstubs) < 1) then link2:='';
      if (link1 = Datawell.Dir('e'))  and (pos('4',currentstubs) < 1) then link2:='';
      if (link1 = Datawell.Dir('se')) and (pos('7',currentstubs) < 1) then link2:='';
      if (link1 = Datawell.Dir('s'))  and (pos('8',currentstubs) < 1) then link2:='';
      if (link1 = Datawell.Dir('sw')) and (pos('9',currentstubs) < 1) then link2:='';
      if (link1 = Datawell.Dir('w'))  and (pos('6',currentstubs) < 1) then link2:='';
      if (link1 = Datawell.Dir('nw')) and (pos('3',currentstubs) < 1) then link2:='';
    end;
    CreateLinkExits(gmap1,gmap2,link1,link2,p1,p2,p3,p4,x1,y1,x2,y2,lcol,true);
    //Check for attic/basement
    FormMap.CheckBasement(gmap1,gmap2,link1,link2);
    SetXY(gmap2,x2,y2);
    if MenuTextChart.checked then begin
      lstr:='-';
      if link1 <> '' then lstr:=lstr+'>';
      if link2 <> '' then lstr:='<'+lstr;
      if forced then lstr2:='(forced)' else lstr2:='';
      FormMain.systemtext(format('Charted%s:(%d,%d)%s(%d,%d)',[lstr2,x1,y1,lstr,x2,y2]),0);
    end;
  end;
  mapundostr:=gmap1+'|'+inttostr(x1)+'|'+inttostr(y1);
  overrideroom:=false;
  currentstubs:='';
  awaitinglabel:='';
  UpdateStubs;
  DrawMap(false);
  UpdateFilters(gmap1);
  HaltFailure;
end;

procedure TFormMap.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  if BtnChart.down then begin
    if messagedlg('Stop charting?',mtconfirmation,[mbyes,mbno],0) = mrno then begin
      canclose:=false;
      FormMain.show;
      exit;
    end;
  end;
  canclose:=true;
  StopCharting;
  Datawell.charting:=false;
  Datawell.following:=false;
end;

function TFormMap.MoveXY(dir: string): boolean;
var x, y: integer;
begin
  result:=false;
  x:=CX; y:=CY;
  if (dir = Datawell.Dir('ne')) or (dir = Datawell.Dir('northeast')) then begin
    inc(x); inc(y);
  end
  else if (dir = Datawell.Dir('se')) or (dir = Datawell.Dir('southeast')) then begin
    inc(x); dec(y);
  end
  else if (dir = Datawell.Dir('sw')) or (dir = Datawell.Dir('southwest')) then begin
    dec(x); dec(y);
  end
  else if (dir = Datawell.Dir('nw')) or (dir = Datawell.Dir('northwest')) then begin
    dec(x); inc(y);
  end
  else if (dir = Datawell.Dir('n')) or (dir = Datawell.Dir('north')) then inc(y)
  else if (dir = Datawell.Dir('e')) or (dir = Datawell.Dir('east')) then inc(x)
  else if (dir = Datawell.Dir('s')) or (dir = Datawell.Dir('south')) then dec(y)
  else if (dir = Datawell.Dir('w')) or (dir = Datawell.Dir('west')) then dec(x);
  if (x <> CX) or (y <> CY) then begin
    SetXY(CMAP,x,y);
    result:=true;
  end;
end;

function TFormMap.TryCurrentExit(dir: string): boolean;
var
  table: TCustomClientDataset;
  map: string;
  x, y: integer;
begin
  table := Datawell.TableExits;
  result := table.Locate('MAP1;X;Y;FEXIT', VarArrayOf([GetMapID(cmap), CX, CY, dir]), []);
  if result then
  begin
    map:=GetMapName(table.fieldbyname('Map2').asInteger);
    x:=table.fieldbyname('X2').asinteger;
    y:=table.fieldbyname('Y2').asinteger;
    if RoomAt(map,x,y) then
    begin
      if (MenuTextChart.checked) and (not BtnFollow.down) then
      begin
        if map = CMAP then
          FormMain.systemtext('Exit exists: moving <'+dir+'> to ('+inttostr(x)+','+
            inttostr(y)+')',0)
        else FormMain.systemtext('Exit exists: moving <'+dir+'> to ('+inttostr(x)+','+
          inttostr(y)+') ['+map+']',0);
      end;
      SetXY(map,x,y);
      if map <> listMaps.text then ChangeMap(map);
    end
    else result:=false;
  end;
end;

procedure TFormMap.CollectStubs(str: string);
var
  i: integer;
  stubs: TStringList;
  element, work: string;
  d1, d2, d3, d4, d5, d6 ,d7, d8, d9: boolean;
begin
  if not awaitingfailure then exit;
  str:=Datawell.CutLeadingSpaces(str);
  str:=Datawell.CutTrailingSpaces(str);
  str:=Datawell.subs('  ',' ',str);
  element:=Datawell.subs(Datawell.specialcode,'',
    Datawell.TableGlobals.fieldbyname('S11').asstring);
  if Datawell.TableGlobals.fieldbyname('S19').asstring <> '' then
    str:=Datawell.subs(Datawell.TableGlobals.fieldbyname('S19').asstring,element,str);
  if element <> ' ' then begin
    element:=Datawell.subs(' ','',element);
    str:=stringReplace(str,' ','', [rfReplaceAll]);
  end;
  d1:=false; d2:=false; d3:=false; d4:=false; d5:=false;
  d6:=false; d7:=false; d8:=false; d9:=false;
  if pos('1',currentstubs) > 0 then d1:=true;
  if pos('2',currentstubs) > 0 then d2:=true;
  if pos('3',currentstubs) > 0 then d3:=true;
  if pos('4',currentstubs) > 0 then d4:=true;
  if pos('5',currentstubs) > 0 then d5:=true;
  if pos('6',currentstubs) > 0 then d6:=true;
  if pos('7',currentstubs) > 0 then d7:=true;
  if pos('8',currentstubs) > 0 then d8:=true;
  if pos('9',currentstubs) > 0 then d9:=true;
  stubs:=TStringList.create;
  if copy(str,length(element),1) = element then str:=copy(str,length(element)+1,99999);
  if copy(str,length(str)-length(element)+1,length(element)) = element then
    str:=copy(str,1,length(str)-length(element));
  if element = '' then begin
    element:=',';
    work:=copy(str,1,1);
    delete(str,1,1);
    while length(str) > 0 do begin
      work:=work+element+copy(str,1,1);
      delete(str,1,1);
    end;
    str:=work;
  end;
  Datawell.explode(str,element,stubs);
  for i:=0 to stubs.count-1 do begin
    if pos(' ',stubs[i]) > 0 then continue;
    stubs[i]:=lowercase(stubs[i]);
    stubs[i]:=Datawell.ANOnly(stubs[i]);
    if (stubs[i] = Datawell.Dir('n')) or (stubs[i] = Datawell.Dir('north')) then d8:=true
    else if (stubs[i] = Datawell.Dir('e')) or (stubs[i] = Datawell.Dir('east')) then d6:=true
    else if (stubs[i] = Datawell.Dir('s')) or (stubs[i] = Datawell.Dir('south')) then d2:=true
    else if (stubs[i] = Datawell.Dir('w')) or (stubs[i] = Datawell.Dir('west')) then d4:=true
    else if (stubs[i] = Datawell.Dir('ne')) or (stubs[i] = Datawell.Dir('northeast')) then d9:=true
    else if (stubs[i] = Datawell.Dir('se')) or (stubs[i] = Datawell.Dir('southeast')) then d3:=true
    else if (stubs[i] = Datawell.Dir('sw')) or (stubs[i] = Datawell.Dir('southwest')) then d1:=true
    else if (stubs[i] = Datawell.Dir('nw')) or (stubs[i] = Datawell.Dir('northwest')) then d7:=true
    else d5:=true;
  end;
  stubs.free;
  currentstubs:='';
  if Datawell.TableGlobals.fieldbyname('L1').asboolean then d5:=false;
  if d1 then currentstubs:=currentstubs+'1';
  if d2 then currentstubs:=currentstubs+'2';
  if d3 then currentstubs:=currentstubs+'3';
  if d4 then currentstubs:=currentstubs+'4';
  if d5 then currentstubs:=currentstubs+'5';
  if d6 then currentstubs:=currentstubs+'6';
  if d7 then currentstubs:=currentstubs+'7';
  if d8 then currentstubs:=currentstubs+'8';
  if d9 then currentstubs:=currentstubs+'9';
  UpdateStubs;
end;

function TFormMap.IsCardinal(dir: string): boolean;
begin
  result:=false;
  if (dir = Datawell.Dir('ne')) or (dir = Datawell.Dir('northeast')) then result:=true
  else if (dir = Datawell.Dir('se')) or (dir = Datawell.Dir('southeast')) then result:=true
  else if (dir = Datawell.Dir('sw')) or (dir = Datawell.Dir('southwest')) then result:=true
  else if (dir = Datawell.Dir('nw')) or (dir = Datawell.Dir('northwest')) then result:=true
  else if (dir = Datawell.Dir('n')) or (dir = Datawell.Dir('north')) then result:=true
  else if (dir = Datawell.Dir('e')) or (dir = Datawell.Dir('east')) then result:=true
  else if (dir = Datawell.Dir('s')) or (dir = Datawell.Dir('south')) then result:=true
  else if (dir = Datawell.Dir('w')) or (dir = Datawell.Dir('west')) then result:=true;
end;

function TFormMap.OppositeDir(dir: string): string;
begin
  result:='';
  if (dir = Datawell.Dir('n')) or (dir = Datawell.Dir('north')) then
    result:=Datawell.Dir('s')
  else if (dir = Datawell.Dir('e')) or (dir = Datawell.Dir('east')) then
    result:=Datawell.Dir('w')
  else if (dir = Datawell.Dir('s')) or (dir = Datawell.Dir('south')) then
    result:=Datawell.Dir('n')
  else if (dir = Datawell.Dir('w')) or (dir = Datawell.Dir('west')) then
    result:=Datawell.Dir('e')
  else if (dir = Datawell.Dir('ne')) or (dir = Datawell.Dir('northeast')) then
    result:=Datawell.Dir('sw')
  else if (dir = Datawell.Dir('se')) or (dir = Datawell.Dir('southeast')) then
    result:=Datawell.Dir('nw')
  else if (dir = Datawell.Dir('sw')) or (dir = Datawell.Dir('southwest')) then
    result:=Datawell.Dir('ne')
  else if (dir = Datawell.Dir('nw')) or (dir = Datawell.Dir('northwest')) then
    result:=Datawell.Dir('se')
  else if (dir = Datawell.Dir('u')) or (dir = Datawell.Dir('up')) then
    result:=Datawell.Dir('d')
  else if (dir = Datawell.Dir('d')) or (dir = Datawell.Dir('down')) then
    result:=Datawell.Dir('u');
end;

procedure TFormMap.AbortChart;
begin
  if not awaitingfailure then exit;
  FormMain.TimerChart.enabled:=false;
  if MenuTextChart.checked then begin
    if refreshingroom then FormMain.systemtext('Room refresh aborted',0)
    else FormMain.systemtext('Chart aborted',0);
  end;
  LabelClock.font.color:=clblack;
  LabelClock.color:=clblack;
  LabelClock.caption:=inttostr(Datawell.TableGlobals.fieldbyname('FailureTimer').asinteger);
  TimerCountdown.enabled:=false;
  currentstubs:='';
  UpdateStubs;
  HaltFailure;
end;

procedure TFormMap.MenuSpeedChartClick(Sender: TObject);
begin
  TMenuItem(sender).checked:=not TMenuItem(sender).checked;
  Datawell.TableGlobals.edit;
  Datawell.TableGlobals.fieldbyname('ChartSpeed').asboolean:=MenuSpeedChart.checked;
  Datawell.TableGlobals.fieldbyname('ChartText').asboolean:=MenuTextChart.checked;
  Datawell.TableGlobals.fieldbyname('ChartFree').asboolean:=MenuFreeChart.checked;
  Datawell.TableGlobals.fieldbyname('ChartCenter').asboolean:=MenuCenterChart.checked;
  Datawell.TableGlobals.post;
  if TMenuItem(sender).tag = 4 then begin
    if MenuCenterChart.checked then begin
      CenterX;
      DrawMap(true);
    end;
  end;
end;

procedure TFormMap.MenuParametersClick(Sender: TObject);
var
  form: TFormCharterFailure;
  linked, card, comp: boolean;
  s1, s2, s3, s4, s5, s11, s19, s12, s13, s14, s15, s16, srefresh, slabel: string;
  amt: integer;
begin
  if awaitingfailure then begin
    messagedlg('You cannot setup while awaiting failure',mtinformation,[mbok],0);
    exit;
  end;
  form:=TFormCharterFailure.create(self);
  form.Edit1.text:=Datawell.subs(Datawell.specialcode,'',
    Datawell.TableGlobals.fieldbyname('Failure1').asstring);
  form.Edit2.text:=Datawell.subs(Datawell.specialcode,'',
    Datawell.TableGlobals.fieldbyname('Failure2').asstring);
  form.Edit3.text:=Datawell.subs(Datawell.specialcode,'',
    Datawell.TableGlobals.fieldbyname('Failure3').asstring);
  form.Edit4.text:=Datawell.subs(Datawell.specialcode,'',
    Datawell.TableGlobals.fieldbyname('Failure4').asstring);
  form.Edit5.text:=Datawell.subs(Datawell.specialcode,'',
    Datawell.TableGlobals.fieldbyname('Failure5').asstring);
  form.Edit11.text:=Datawell.subs(Datawell.specialcode,'',
    Datawell.TableGlobals.fieldbyname('S11').asstring);
  form.Edit19.text:=Datawell.subs(Datawell.specialcode,'',
    Datawell.TableGlobals.fieldbyname('S19').asstring);
  form.Edit12.text:=Datawell.TableGlobals.fieldbyname('S12').asstring;
  form.Edit13.text:=Datawell.TableGlobals.fieldbyname('S13').asstring;
  form.Edit14.text:=Datawell.TableGlobals.fieldbyname('S14').asstring;
  form.Edit15.text:=Datawell.TableGlobals.fieldbyname('S15').asstring;
  form.Edit16.text:=Datawell.TableGlobals.fieldbyname('S16').asstring;
  form.EditRefresh.text:=Datawell.TableGlobals.fieldbyname('S1').asstring;
  form.Spin1.value:=Datawell.TableGlobals.fieldbyname('FailureTimer').asinteger;
  form.CheckNonCardinal.checked:=Datawell.TableGlobals.fieldbyname('L1').asboolean;
  form.CheckCompleteonStub.checked:=Datawell.TableGlobals.fieldbyname('L2').asboolean;
  form.EditLabel.text:=Datawell.TableGlobals.fieldbyname('S20').asstring;
  form.EditU.text:=Datawell.TableGlobals2.fieldbyname('Up').asstring;
  form.EditD.text:=Datawell.TableGlobals2.fieldbyname('Down').asstring;
  form.EditNW.text:=Datawell.TableGlobals2.fieldbyname('Northwest').asstring;
  form.EditN.text:=Datawell.TableGlobals2.fieldbyname('North').asstring;
  form.EditNE.text:=Datawell.TableGlobals2.fieldbyname('Northeast').asstring;
  form.EditW.text:=Datawell.TableGlobals2.fieldbyname('West').asstring;
  form.EditE.text:=Datawell.TableGlobals2.fieldbyname('East').asstring;
  form.EditSW.text:=Datawell.TableGlobals2.fieldbyname('Southwest').asstring;
  form.EditS.text:=Datawell.TableGlobals2.fieldbyname('South').asstring;
  form.EditSE.text:=Datawell.TableGlobals2.fieldbyname('Southeast').asstring;
  form.EditU2.text:=Datawell.TableGlobals2.fieldbyname('U').asstring;
  form.EditD2.text:=Datawell.TableGlobals2.fieldbyname('D').asstring;
  form.EditNW2.text:=Datawell.TableGlobals2.fieldbyname('NW').asstring;
  form.EditN2.text:=Datawell.TableGlobals2.fieldbyname('N').asstring;
  form.EditNE2.text:=Datawell.TableGlobals2.fieldbyname('NE').asstring;
  form.EditW2.text:=Datawell.TableGlobals2.fieldbyname('W').asstring;
  form.EditE2.text:=Datawell.TableGlobals2.fieldbyname('E').asstring;
  form.EditSW2.text:=Datawell.TableGlobals2.fieldbyname('SW').asstring;
  form.EditS2.text:=Datawell.TableGlobals2.fieldbyname('S').asstring;
  form.EditSE2.text:=Datawell.TableGlobals2.fieldbyname('SE').asstring;
  form.showmodal;
  amt:=form.amt;
  s1:=form.Edit1.text;
  s2:=form.Edit2.text;
  s3:=form.Edit3.text;
  s4:=form.Edit4.text;
  s5:=form.Edit5.text;
  s11:=form.Edit11.text;
  s19:=form.Edit19.text;
  s12:=form.Edit12.text;
  s13:=form.Edit13.text;
  s14:=form.Edit14.text;
  s15:=form.Edit15.text;
  s16:=form.Edit16.text;
  srefresh:=form.EditRefresh.text;
  card:=form.CheckNonCardinal.checked;
  comp:=form.CheckCompleteonStub.checked;
  slabel:=form.EditLabel.text;
  linked:=form.linked;
  if linked then begin
    if copy(s1,length(s1),1) = ' ' then s1:=s1+Datawell.specialcode;
    if copy(s2,length(s2),1) = ' ' then s2:=s2+Datawell.specialcode;
    if copy(s3,length(s3),1) = ' ' then s3:=s3+Datawell.specialcode;
    if copy(s4,length(s4),1) = ' ' then s4:=s4+Datawell.specialcode;
    if copy(s5,length(s5),1) = ' ' then s5:=s5+Datawell.specialcode;
    if copy(s11,length(s11),1) = ' ' then s11:=s11+Datawell.specialcode;
    Datawell.TableGlobals.edit;
    Datawell.TableGlobals.fieldbyname('Failure1').asstring:=s1;
    Datawell.TableGlobals.fieldbyname('Failure2').asstring:=s2;
    Datawell.TableGlobals.fieldbyname('Failure3').asstring:=s3;
    Datawell.TableGlobals.fieldbyname('Failure4').asstring:=s4;
    Datawell.TableGlobals.fieldbyname('Failure5').asstring:=s5;
    Datawell.TableGlobals.fieldbyname('S11').asstring:=s11;
    Datawell.TableGlobals.fieldbyname('S19').asstring:=s19;
    Datawell.TableGlobals.fieldbyname('S12').asstring:=s12;
    Datawell.TableGlobals.fieldbyname('S13').asstring:=s13;
    Datawell.TableGlobals.fieldbyname('S14').asstring:=s14;
    Datawell.TableGlobals.fieldbyname('S15').asstring:=s15;
    Datawell.TableGlobals.fieldbyname('S16').asstring:=s16;
    Datawell.TableGlobals.fieldbyname('FailureTimer').asinteger:=amt;
    Datawell.TableGlobals.fieldbyname('S1').asstring:=srefresh;
    Datawell.TableGlobals.fieldbyname('L1').asboolean:=card;
    Datawell.TableGlobals.fieldbyname('L2').asboolean:=comp;
    Datawell.TableGlobals.fieldbyname('S20').asstring:=slabel;
    Datawell.TableGlobals.post;
    Datawell.TableGlobals2.edit;
    Datawell.TableGlobals2.fieldbyname('Up').asstring:=form.EditU.text;
    Datawell.TableGlobals2.fieldbyname('Down').asstring:=form.EditD.text;
    Datawell.TableGlobals2.fieldbyname('Northwest').asstring:=form.EditNW.text;
    Datawell.TableGlobals2.fieldbyname('North').asstring:=form.EditN.text;
    Datawell.TableGlobals2.fieldbyname('Northeast').asstring:=form.EditNE.text;
    Datawell.TableGlobals2.fieldbyname('West').asstring:=form.EditW.text;
    Datawell.TableGlobals2.fieldbyname('East').asstring:=form.EditE.text;
    Datawell.TableGlobals2.fieldbyname('Southwest').asstring:=form.EditSW.text;
    Datawell.TableGlobals2.fieldbyname('South').asstring:=form.EditS.text;
    Datawell.TableGlobals2.fieldbyname('Southeast').asstring:=form.EditSE.text;
    Datawell.TableGlobals2.fieldbyname('U').asstring:=form.EditU2.text;
    Datawell.TableGlobals2.fieldbyname('D').asstring:=form.EditD2.text;
    Datawell.TableGlobals2.fieldbyname('NW').asstring:=form.EditNW2.text;
    Datawell.TableGlobals2.fieldbyname('N').asstring:=form.EditN2.text;
    Datawell.TableGlobals2.fieldbyname('NE').asstring:=form.EditNE2.text;
    Datawell.TableGlobals2.fieldbyname('W').asstring:=form.EditW2.text;
    Datawell.TableGlobals2.fieldbyname('E').asstring:=form.EditE2.text;
    Datawell.TableGlobals2.fieldbyname('SW').asstring:=form.EditSW2.text;
    Datawell.TableGlobals2.fieldbyname('S').asstring:=form.EditS2.text;
    Datawell.TableGlobals2.fieldbyname('SE').asstring:=form.EditSE2.text;
    Datawell.TableGlobals2.post;
    LabelClock.caption:=inttostr(Datawell.TableGlobals.fieldbyname('FailureTimer').asinteger);
  end;
  form.free;
end;

procedure TFormMap.MenuBasementClick(Sender: TObject);
var newmap: string;
begin
  if TMenuItem(sender).tag = 1 then newmap:=dsCurrentMap.fieldbyname('S2').asstring
  else newmap:=dsCurrentMap.fieldbyname('S3').asstring;
  if newmap = '' then begin
    messagedlg('This map has no '+TMenuItem(sender).hint+
      ' assigned.  You can assign one via Map Properties',
      mtinformation,[mbok],0);
    exit;
  end;
  ChangeMap(newmap);
end;

procedure TFormMap.MenuLineAtticClick(Sender: TObject);
var
  linemap: string;
  table: TClientDataset;
  didfind: boolean;
begin
  if TMenuItem(sender).tag = 1 then linemap:=dsCurrentMap.fieldbyname('S2').asstring
  else linemap:=dsCurrentMap.fieldbyname('S3').asstring;
  if linemap = '' then begin
    messagedlg('This map has no '+TMenuItem(sender).hint+
      ' assigned.  You can assign one via Map Properties',
      mtinformation,[mbok],0);
    exit;
  end;
  table:=TClientDataset.create(self);
  with table do begin
    CloneCursor(Datawell.TableMaps, true);
    active:=true;
    didfind:=false;
    first;
    while not EOF do begin
      if fieldbyname('Map').asstring = linemap then begin
        didfind:=true;
        break;
      end;
      next;
    end;
    if didfind then begin
      edit;
      fieldbyname('Zoom').asinteger:=dsCurrentMap.fieldbyname('Zoom').asinteger;
      fieldbyname('HomeX').asinteger:=dsCurrentMap.fieldbyname('HomeX').asinteger;
      fieldbyname('HomeY').asinteger:=dsCurrentMap.fieldbyname('HomeY').asinteger;
      post;
    end;
  end;
  table.free;
end;

procedure TFormMap.BtnAtticClick(Sender: TObject);
begin
  if TComponent(sender).tag = 1 then MenuBasement.click else MenuAttic.click;
end;

procedure TFormMap.BtnEditClick(Sender: TObject);
begin
  MenuEdit.checked:=BtnEdit.down;
  DrawMap(false);
end;

procedure TFormMap.UpdateBasementButtons;
begin
  if dsCurrentMap.fieldbyname('S2').asstring <> '' then begin
    BtnBasement.enabled:=true;
    BtnBasement.hint:='Switch to this map level''s basement ('+
      dsCurrentMap.fieldbyname('S2').asstring+')';
    MenuViewBasement.enabled:=true;
    MenuViewBasement.caption:='View &Basement ('+
      dsCurrentMap.fieldbyname('S2').asstring+')';
  end
  else begin
    BtnBasement.enabled:=false;
    MenuViewBasement.enabled:=false;
    MenuViewBasement.caption:='View &Basement';
  end;
  if dsCurrentMap.fieldbyname('S3').asstring <> '' then begin
    BtnAttic.enabled:=true;
    BtnAttic.hint:='Switch to this map level''s Attic ('+
      dsCurrentMap.fieldbyname('S3').asstring+')';
    MenuViewAttic.enabled:=true;
    MenuViewAttic.caption:='View &Attic ('+
      dsCurrentMap.fieldbyname('S3').asstring+')';
  end
  else begin
    BtnAttic.enabled:=false;
    MenuViewAttic.enabled:=false;
    MenuViewAttic.caption:='View &Attic';
  end;
end;

procedure TFormMap.CheckBasement(map1, map2, link1, link2: string);
var
  table: TClientDataset;
  checkingattic, checkingbasement, didfind, setmap: boolean;
begin
  if map1 = map2 then exit;
  checkingattic:=((lowercase(copy(link1,1,1)) = 'u') or (lowercase(copy(link2,1,1)) = 'd'));
  checkingbasement:=((lowercase(copy(link1,1,1)) = 'd') or (lowercase(copy(link2,1,1)) = 'u'));
  if (not checkingattic) and (not checkingbasement) then exit;
  table:=TClientDataset.create(self);
  with table do begin
    CloneCursor(Datawell.TableMaps, true);
    active:=true;
    if checkingattic then begin
      //Check this map's attic
      didfind:=false;
      first;
      while not EOF do begin
        if fieldbyname('Map').asstring = map1 then begin
          didfind:=true;
          break;
        end;
        next;
      end;
      if didfind then begin
        setmap:=false;
        if fieldbyname('S3').asstring = '' then setmap:=true
        else begin
          if fieldbyname('S3').asstring <> map2 then begin
            if messagedlg('This map level '+map1+'''s attic is currently set to: '+
              fieldbyname('S3').asstring+#13+
              'Do you wish to overwrite it with '+map2+'?',
              mtconfirmation,[mbyes,mbno],0) = mryes then setmap:=true;
          end;
        end;
        if setmap then begin
          edit;
          fieldbyname('S3').asstring:=map2;
          post;
        end;
      end;
      //Check this attic's basement level
      didfind:=false;
      first;
      while not EOF do begin
        if fieldbyname('Map').asstring = map2 then begin
          didfind:=true;
          break;
        end;
        next;
      end;
      if didfind then begin
        setmap:=false;
        if fieldbyname('S2').asstring = '' then setmap:=true
        else begin
          if fieldbyname('S2').asstring <> map1 then begin
            if messagedlg('Map level '+map2+'''s basement is currently set to: '+
              fieldbyname('S2').asstring+#13+
              'Do you wish to overwrite it with this map? ('+map1+')',
              mtconfirmation,[mbyes,mbno],0) = mryes then setmap:=true;
          end;
        end;
        if setmap then begin
          edit;
          fieldbyname('S2').asstring:=map1;
          post;
        end;
      end;
    end;
    if checkingbasement then begin
      //Check this map's basement
      didfind:=false;
      first;
      while not EOF do begin
        if fieldbyname('Map').asstring = map1 then begin
          didfind:=true;
          break;
        end;
        next;
      end;
      if didfind then begin
        setmap:=false;
        if fieldbyname('S2').asstring = '' then setmap:=true
        else begin
          if fieldbyname('S2').asstring <> map2 then begin
            if messagedlg('This map level '+map1+'''s basement is currently set to: '+
              fieldbyname('S2').asstring+#13+
              'Do you wish to overwrite it with '+map2+'?',
              mtconfirmation,[mbyes,mbno],0) = mryes then setmap:=true;
          end;
        end;
        if setmap then begin
          edit;
          fieldbyname('S2').asstring:=map2;
          post;
        end;
      end;
      //Check this basement's attic level
      didfind:=false;
      first;
      while not EOF do begin
        if fieldbyname('Map').asstring = map2 then begin
          didfind:=true;
          break;
        end;
        next;
      end;
      if didfind then begin
        setmap:=false;
        if fieldbyname('S3').asstring = '' then setmap:=true
        else begin
          if fieldbyname('S3').asstring <> map1 then begin
            if messagedlg('Map level '+map2+'''s attic is currently set to: '+
              fieldbyname('S3').asstring+#13+
              'Do you wish to overwrite it with this map? ('+map1+')',
              mtconfirmation,[mbyes,mbno],0) = mryes then setmap:=true;
          end;
        end;
        if setmap then begin
          edit;
          fieldbyname('S3').asstring:=map1;
          post;
        end;
      end;
    end;
  end;
  table.free;
  UpdateBasementButtons;
end;

procedure TFormMap.MenuEditClick(Sender: TObject);
begin
  if listMaps.text = '' then exit;
  TMenuItem(sender).checked:=not TMenuItem(sender).checked;
  BtnEdit.down:=MenuEdit.checked;
  DrawMap(false);
end;

function TFormMap.CountMaps: integer;
var table: TClientDataset;
begin
  result:=0;
  table:=TClientDataset.create(self);
  with table do begin
    CloneCursor(Datawell.TableMaps, true);
    active:=true;
    first;
    while not EOF do begin
      inc(result);
      next;
    end;
  end;
  table.free;
end;

procedure TFormMap.MenuZeroClick(Sender: TObject);
begin
  CenterX;
end;

procedure TFormMap.MenuRenameMapClick(Sender: TObject);
var
  form: TFormRenameCanvas;
  oldmap, newmap: string;
  table: TClientDataset;
  didfind, completed: boolean;
begin
  if listMaps.text = '' then exit;
  oldmap:=listMaps.text;
  form:=TFormRenameCanvas.create(self);
  form.caption:='Rename Map Level: '+listMaps.text;
  form.Label1.caption:='&New Level Name';
  form.EditCaption.charcase:=ecuppercase;
  form.showmodal;
  newmap:=form.cap;
  form.free;
  if newmap = '<nIl>' then exit;
  //Check for existing map
  table:=TClientDataset.create(self);
  with table do begin
    CloneCursor(Datawell.TableMaps, true);
    active:=true;
    didfind:=false;
    first;
    while not EOF do begin
      if fieldbyname('Map').asstring = newmap then begin
        didfind:=true;
        break;
      end;
      next;
    end;
  end;
  table.free;
  if didfind then begin
    messagedlg('Map '+newmap+' already exists',mtinformation,[mbok],0);
    exit;
  end;
  if Datawell.TableGlobals.fieldbyname('ChartLastMap').asstring = oldmap then begin
    Datawell.TableGlobals.edit;
    Datawell.TableGlobals.fieldbyname('ChartLastMap').asstring:=newmap;
    Datawell.TableGlobals.post;
    Datawell.TableGlobals.ApplyUpdates(1);
  end;
  table:=TClientDataset.create(self);
  with table do begin
    CloneCursor(Datawell.TableMaps, true);
    active:=true;
    completed:=false;
    while not completed do begin
      completed:=true;
      first;
      while not EOF do begin
        if (fieldbyname('Map').asstring = oldmap) or
          (fieldbyname('S2').asstring = oldmap) or
          (fieldbyname('S3').asstring = oldmap) then begin
          edit;
          if fieldbyname('Map').asstring = oldmap then fieldbyname('Map').asstring:=newmap;
          if fieldbyname('S2').asstring = oldmap then fieldbyname('S2').asstring:=newmap;
          if fieldbyname('S3').asstring = oldmap then fieldbyname('S3').asstring:=newmap;
          post;
          completed:=false;
        end;
        next;
      end;
    end;
    //Change rooms
    CloneCursor(Datawell.TableRooms, false);
    filter := 'Map = '''+oldmap+'''';
    filtered := true;
    active:=true;
    completed:=false;
    while not completed do begin
      completed:=true;
      first;
      while not EOF do begin
        if fieldbyname('Map').asstring = oldmap then begin
          edit;
          fieldbyname('Map').asstring:=newmap;
          post;
          completed:=false;
        end;
        next;
      end;
    end;
    active:=false;
    //Change origination exits
    CloneCursor(Datawell.TableExits, false);
    filter := 'Map = '''+oldmap+'''';
    filtered := true;
    active:=true;
    completed:=false;
    while not completed do begin
      completed:=true;
      first;
      while not EOF do begin
        if fieldbyname('Map').asstring = oldmap then begin
          edit;
          fieldbyname('Map').asstring:=newmap;
          post;
          completed:=false;
        end;
        next;
      end;
    end;
    active:=false;
    //Change destination exits
    CloneCursor(Datawell.TableExits, false);
    filter := 'Map2 = '''+oldmap+'''';
    filtered := true;
    active:=true;
    completed:=false;
    while not completed do begin
      completed:=true;
      first;
      while not EOF do begin
        if fieldbyname('Map2').asstring = oldmap then begin
          edit;
          fieldbyname('Map2').asstring:=newmap;
          post;
          completed:=false;
        end;
        next;
      end;
    end;
  end;
  table.free;
  ChangeMap(newmap);
  messagedlg('Map renamed to: '+newmap,mtinformation,[mbok],0);
end;

function TFormMap.FigureAB(const map, dir: string): string;
var
  table: TCustomClientDataset;
begin
  result:='';
  table:=datawell.TableMaps;
  if table.Locate('Map', map, []) then begin
    if dir = 'a' then result:=table.fieldbyname('S3').asstring
    else result:=table.fieldbyname('S2').asstring
  end;
end;

procedure TFormMap.MenuBatchDeleteClick(Sender: TObject);
var
  form: TFormMapDelete;
  table: TClientDataset;
begin
  if not Datawell.CheckRinged2('batch delete map levels') then exit;
  form:=TFormMapDelete.create(self);
  table:=TClientDataset.create(self);
  with table do begin
    CloneCursor(Datawell.TableMaps, true);
    active:=true;
    first;
    while not EOF do begin
      form.ListMaps.items.add(fieldbyname('Map').asstring);
      next;
    end;
  end;
  table.free;
  form.showmodal;
  if form.diddelete then begin
    Undos.clear;
    MenuUndo.enabled:=false;
    listMaps.text:='';
    Datawell.TableMaps.first;
    if ChangeMap(Datawell.TableGlobals.fieldbyname('ChartLastMap').asstring) then exit;
    if Datawell.TableMaps.fieldbyname('Map').asstring = '' then begin
      WipeMap;
      form.free;
      exit;
    end;
    if not ChangeMap(Datawell.TableMaps.fieldbyname('Map').asstring) then WipeMap;
    messagedlg('Maps deleted',mtinformation,[mbok],0);
  end;
  form.free;
end;

procedure TFormMap.SaveBitmapImageofMap1Click(Sender: TObject);
var failed: boolean;
begin
  if not Datawell.CheckRinged2('save bitmap image of map') then exit;
  DialogSave.initialdir:=Datawell.GetDir(3);
  DialogSave.filename:='Cartographer1.bmp';
  if not DialogSave.execute then exit;
  Datawell.SetDir(3,DialogSave.filename);
  failed:=false;
  try mapcanvas.picture.savetofile(DialogSave.filename);
    except on EFCreateError do failed:=true; end;
  if failed then
    messagedlg('Error in saving bitmap file',mtinformation,[mbok],0)
  else messagedlg('Bitmap '+DialogSave.filename+' saved',mtinformation,[mbok],0);
end;

procedure TFormMap.MenuFindTextClick(Sender: TObject);
var
  table: TClientDataset;
  lines, op: TStringList;
  find, output: string;
  i: integer;
  form: TFormEnterText;
begin
  assert(false); // TODO: deal with this later

  if not Datawell.CheckRinged(false,'find text in room labels/memos') then exit;
  form:=TFormEnterText.create(self);
  form.showmodal;
  find:=form.replacement;
  form.free;
  if find = '' then exit;
  lines:=TStringList.create;
  op:=TStringList.create;
  table:=TClientDataset.create(self);
  with table do begin
    //Check Room Labels
    CloneCursor(Datawell.TableRooms, true);
    active:=true;
    first;
    while not EOF do begin
      if fieldbyname('Map').asstring = listMaps.text then begin
        if pos(find,lowercase(fieldbyname('Label').asstring)) > 0 then
          op.add('('+inttostr(fieldbyname('X').asinteger)+
          ','+inttostr(fieldbyname('Y').asinteger)+') Label');
      end;
      next;
    end;
    {//Check Room Memos
    CloneCursor(Datawell.TableRMemos, true);
    active:=true;
    first;
    while not EOF do begin
      if fieldbyname('Map').asstring = EditMap.text then begin
        didfind:=false;
        lines.assign(fieldbyname('Memo'));
        for i:=0 to lines.count-1 do begin
          if pos(find,lowercase(lines[i])) > 0 then begin
            didfind:=true;
            break;
          end;
        end;
        if didfind then op.add('('+inttostr(fieldbyname('X').asinteger)+
          ','+inttostr(fieldbyname('Y').asinteger)+') Memo');
      end;
      next;
    end; }
  end;
  table.free;
  lines.free;
  output:='Text to find: '+find+#13+#13;
  for i:=0 to op.count-1 do output:=output+op[i]+#13;
  output:=output+#13+'Total room matches: '+inttostr(op.count);
  op.free;
  messagedlg(output,mtinformation,[mbok],0);
end;

procedure TFormMap.CopySprint(a1m2, x, y: integer; grab: string; cmds: TStringList);
var didfind: boolean;
begin
  if a1m2 = 1 then begin
    with Datawell.TableAliases do begin
      first;
      didfind:=false;
      while not EOF do begin
        if fieldbyname('Alias').asstring = grab then begin
          didfind:=true;
          break;
        end;
        next;
      end;
      if didfind then begin
        edit;
        fieldbyname('Script').assign(cmds);
        post;
      end
      else begin
        append;
        fieldbyname('Master').asinteger:=Datawell.masterinteger;
        fieldbyname('Alias').asstring:=grab;
        fieldbyname('Script').assign(cmds);
        post;
      end;
    end;
    messagedlg('Alias "'+grab+'" populated with sprint to room ('+
      inttostr(x)+','+inttostr(y)+') [Map: '+dsCurrentMap.fieldbyname('Map').asstring+']',mtinformation,[mbok],0);
  end
  else begin
    with Datawell.TableMacros do begin
      first;
      didfind:=false;
      while not EOF do begin
        if fieldbyname('Macro').asstring = grab then begin
          didfind:=true;
          break;
        end;
        next;
      end;
      if didfind then begin
        edit;
        fieldbyname('Script').assign(cmds);
        post;
      end;
    end;
    messagedlg('Macro "'+grab+'" populated with sprint to room ('+
      inttostr(x)+','+inttostr(y)+') [Map: '+dsCurrentMap.fieldbyname('Map').asstring+']',mtinformation,[mbok],0);
   end;
end;

end.
